QRurl.us
Torey Heinz
committed Aug 22, 2025
commit 9cfc8c23144affc85a6395bf5fbad05e674b46a0
Showing 43
changed files with
8365 additions
and 0 deletions
.dev.vars.example
+3
-0
| @@ | @@ -0,0 +1,3 @@ |
| + | JWT_SECRET=your-secret-key-change-in-production |
| + | EMAIL_API_KEY=your-resend-or-sendgrid-api-key |
| + | AUTHORIZED_EMAILS=admin@example.com,user@example.com |
| \ No newline at end of file | |
.gitignore
+36
-0
| @@ | @@ -0,0 +1,36 @@ |
| + | # Dependencies |
| + | node_modules/ |
| + | |
| + | # Environment files |
| + | .env |
| + | .env.* |
| + | .dev.vars |
| + | |
| + | # Wrangler |
| + | .wrangler/ |
| + | .dev.vars |
| + | |
| + | # Build output |
| + | dist/ |
| + | build/ |
| + | |
| + | # Logs |
| + | *.log |
| + | npm-debug.log* |
| + | |
| + | # OS files |
| + | .DS_Store |
| + | Thumbs.db |
| + | |
| + | # IDE |
| + | .idea/ |
| + | .vscode/ |
| + | *.swp |
| + | *.swo |
| + | |
| + | # Test coverage |
| + | coverage/ |
| + | |
| + | # Local database |
| + | *.sqlite |
| + | *.sqlite3 |
| \ No newline at end of file | |
README.md
+201
-0
| @@ | @@ -0,0 +1,201 @@ |
| + | # QRurl - URL Shortener with QR Codes |
| + | |
| + | A modern, serverless URL shortener with QR code generation built on Cloudflare Workers, D1, and R2. |
| + | |
| + | ## Features |
| + | |
| + | - ๐ **Custom Short URLs** - Create memorable short links with custom slugs |
| + | - ๐ฑ **QR Code Generation** - Generate QR codes with optional logo embedding |
| + | - ๐ **Magic Link Authentication** - Passwordless login via email |
| + | - ๐ **Analytics Dashboard** - Track clicks, geographic data, and referrers |
| + | - โก **Edge Performance** - Global low-latency redirects via Cloudflare Workers |
| + | - ๐พ **Serverless Architecture** - Cost-efficient with automatic scaling |
| + | |
| + | ## Tech Stack |
| + | |
| + | ### Backend |
| + | - **Cloudflare Workers** - Edge compute platform |
| + | - **D1 Database** - SQLite-based serverless database |
| + | - **R2 Storage** - Object storage for images and QR codes |
| + | - **KV Namespace** - Key-value storage for caching |
| + | |
| + | ### Frontend |
| + | - **Vue 3** - Progressive JavaScript framework |
| + | - **Vite** - Fast build tool |
| + | - **Tailwind CSS** - Utility-first CSS framework |
| + | - **Vue Router** - Client-side routing |
| + | - **Pinia** - State management |
| + | - **Axios** - HTTP client |
| + | |
| + | ## Getting Started |
| + | |
| + | ### Prerequisites |
| + | - Node.js 18+ and npm |
| + | - Cloudflare account |
| + | - Wrangler CLI (`npm install -g wrangler`) |
| + | |
| + | ### Installation |
| + | |
| + | 1. Clone the repository: |
| + | ```bash |
| + | git clone https://github.com/yourusername/qrurl.git |
| + | cd qrurl |
| + | ``` |
| + | |
| + | 2. Install backend dependencies: |
| + | ```bash |
| + | npm install |
| + | ``` |
| + | |
| + | 3. Install frontend dependencies: |
| + | ```bash |
| + | cd frontend |
| + | npm install |
| + | cd .. |
| + | ``` |
| + | |
| + | 4. Configure Cloudflare services: |
| + | ```bash |
| + | # Login to Cloudflare |
| + | wrangler login |
| + | |
| + | # The D1 database, R2 bucket, and KV namespace are already configured |
| + | # Check wrangler.toml for the configuration |
| + | ``` |
| + | |
| + | 5. Set up environment variables: |
| + | ```bash |
| + | # Backend (.dev.vars) |
| + | cp .dev.vars.example .dev.vars |
| + | # Edit .dev.vars with your configuration |
| + | |
| + | # Frontend (frontend/.env) |
| + | cp frontend/.env.example frontend/.env |
| + | # Edit frontend/.env if needed |
| + | ``` |
| + | |
| + | 6. Initialize the database: |
| + | ```bash |
| + | # Local database |
| + | npm run db:init |
| + | |
| + | # Remote database (production) |
| + | npm run db:init:remote |
| + | ``` |
| + | |
| + | ### Development |
| + | |
| + | 1. Start the backend (Cloudflare Workers): |
| + | ```bash |
| + | npm run dev |
| + | # Backend runs on http://localhost:8787 |
| + | ``` |
| + | |
| + | 2. In a new terminal, start the frontend: |
| + | ```bash |
| + | cd frontend |
| + | npm run dev |
| + | # Frontend runs on http://localhost:3000 |
| + | ``` |
| + | |
| + | 3. Open http://localhost:3000 in your browser |
| + | |
| + | ### API Endpoints |
| + | |
| + | #### Authentication |
| + | - `POST /api/auth/request` - Request magic link |
| + | - `POST /api/auth/verify` - Verify magic link token |
| + | - `POST /api/auth/logout` - Logout |
| + | |
| + | #### Entries (Protected) |
| + | - `GET /api/entries` - List user's entries |
| + | - `POST /api/entries` - Create new entry |
| + | - `GET /api/entries/:id` - Get entry details |
| + | - `PUT /api/entries/:id` - Update entry |
| + | - `DELETE /api/entries/:id` - Delete entry |
| + | |
| + | #### Analytics (Protected) |
| + | - `GET /api/analytics/:id` - Get entry analytics |
| + | |
| + | #### Public |
| + | - `GET /health` - Health check |
| + | - `GET /:slug` - Redirect to original URL |
| + | |
| + | ### Deployment |
| + | |
| + | 1. Deploy the backend to Cloudflare Workers: |
| + | ```bash |
| + | npm run deploy |
| + | ``` |
| + | |
| + | 2. Build and deploy the frontend: |
| + | ```bash |
| + | cd frontend |
| + | npm run build |
| + | # Deploy the dist folder to Cloudflare Pages or your preferred hosting |
| + | ``` |
| + | |
| + | ### Configuration |
| + | |
| + | #### Email Service |
| + | The application uses magic links for authentication. Configure your email service provider in `.dev.vars`: |
| + | - Resend: Set `EMAIL_API_KEY` with your Resend API key |
| + | - SendGrid: Set `EMAIL_API_KEY` with your SendGrid API key |
| + | |
| + | #### Authorized Emails |
| + | Add authorized emails in: |
| + | 1. Environment variable: `AUTHORIZED_EMAILS` (comma-separated) |
| + | 2. Database: `authorized_emails` table |
| + | |
| + | ## Project Structure |
| + | |
| + | ``` |
| + | qrurl/ |
| + | โโโ src/ # Backend source code |
| + | โ โโโ index.js # Main worker entry |
| + | โ โโโ routes/ # API route handlers |
| + | โ โโโ middleware/ # Auth, CORS, rate limiting |
| + | โ โโโ lib/ # Database operations |
| + | โ โโโ utils/ # Utilities |
| + | โโโ frontend/ # Vue.js frontend |
| + | โ โโโ src/ |
| + | โ โ โโโ views/ # Page components |
| + | โ โ โโโ components/ # Reusable components |
| + | โ โ โโโ stores/ # Pinia stores |
| + | โ โ โโโ services/ # API and QR services |
| + | โ โ โโโ router/ # Vue Router config |
| + | โ โโโ public/ |
| + | โโโ schema/ # Database schema |
| + | โโโ dev/ # Development docs |
| + | โโโ wrangler.toml # Cloudflare Workers config |
| + | ``` |
| + | |
| + | ## Security |
| + | |
| + | - JWT-based authentication with secure tokens |
| + | - Magic link authentication (passwordless) |
| + | - Email whitelist for access control |
| + | - Rate limiting on API endpoints |
| + | - Input validation and sanitization |
| + | - CORS protection |
| + | - SQL injection prevention |
| + | |
| + | ## Performance |
| + | |
| + | - Edge deployment for global low latency |
| + | - KV caching for frequently accessed data |
| + | - Optimized database queries with indexes |
| + | - QR code caching in R2 |
| + | - Lazy loading in frontend |
| + | |
| + | ## License |
| + | |
| + | MIT |
| + | |
| + | ## Contributing |
| + | |
| + | Pull requests are welcome! Please read the contributing guidelines first. |
| + | |
| + | ## Support |
| + | |
| + | For issues and questions, please use the GitHub issues page. |
| \ No newline at end of file | |
dev/qrurl-mvp.md
+228
-0
| @@ | @@ -0,0 +1,228 @@ |
| + | # Feature Plan: QRurl MVP Implementation |
| + | |
| + | **Status**: Planning |
| + | **Created**: 2025-08-22 |
| + | **Author**: Claude |
| + | |
| + | ## Overview |
| + | Building a serverless URL shortener with QR code generation capabilities using Cloudflare Workers, D1 database, and R2 storage. The MVP will include magic link authentication, custom branded QR codes, basic analytics, and a simple management interface. |
| + | |
| + | ## Requirements |
| + | - URL shortening with custom slugs |
| + | - QR code generation with logo embedding |
| + | - Magic link authentication (passwordless) |
| + | - Email whitelist for access control |
| + | - Basic analytics tracking (clicks, referrers, geographic data) |
| + | - Admin interface for managing links |
| + | - Serverless deployment on Cloudflare Workers |
| + | |
| + | ## Technical Approach |
| + | Leverage Cloudflare's edge infrastructure for global low-latency redirects. Use D1 for data persistence, R2 for image/QR storage, and Workers for all compute. Frontend will be a SPA served from Workers or Pages. |
| + | |
| + | ### Architecture Decisions |
| + | - **Cloudflare Workers**: Chosen for edge performance and cost-efficiency |
| + | - **D1 Database**: SQLite-based, perfect for read-heavy workloads |
| + | - **R2 Storage**: Cost-effective object storage for QR codes and logos |
| + | - **Magic Links**: Simpler than password management, better UX |
| + | - **QR with High Error Correction**: Allows 30% obstruction for logo overlay |
| + | |
| + | ## Implementation Tasks |
| + | |
| + | ### Phase 1: Foundation (Week 1) |
| + | - [ ] Initialize Cloudflare Workers project with Wrangler |
| + | - [ ] Set up development environment and local testing |
| + | - [ ] Create D1 database and implement schema |
| + | - [ ] Configure R2 bucket for storage |
| + | - [ ] Set up basic routing structure |
| + | - [ ] Implement health check endpoint |
| + | - [ ] Configure environment variables and secrets |
| + | |
| + | ### Phase 2: Core URL Functionality (Week 1-2) |
| + | - [ ] Implement slug generation (nanoid with custom alphabet) |
| + | - [ ] Create URL entry CRUD operations |
| + | - [ ] Build redirect handler with analytics capture |
| + | - [ ] Add URL validation and sanitization |
| + | - [ ] Implement slug collision handling |
| + | - [ ] Create basic rate limiting |
| + | - [ ] Write unit tests for URL operations |
| + | |
| + | ### Phase 3: Authentication System (Week 2) |
| + | - [ ] Design magic link token generation |
| + | - [ ] Create email whitelist management |
| + | - [ ] Implement token storage with expiry (15 min) |
| + | - [ ] Build email sending integration (Resend/SendGrid) |
| + | - [ ] Create JWT session management |
| + | - [ ] Add authentication middleware |
| + | - [ ] Implement logout functionality |
| + | - [ ] Write auth system tests |
| + | |
| + | ### Phase 4: QR Code Generation (Week 3) |
| + | - [ ] Research and select QR library for Workers |
| + | - [ ] Implement basic QR code generation |
| + | - [ ] Add logo overlay functionality (max 30% area) |
| + | - [ ] Create QR caching strategy in R2 |
| + | - [ ] Build image upload and processing |
| + | - [ ] Implement logo resizing (128x128 standard) |
| + | - [ ] Add QR customization options (colors, margins) |
| + | - [ ] Test QR scanning reliability with logos |
| + | |
| + | ### Phase 5: Analytics & Tracking (Week 3-4) |
| + | - [ ] Design analytics data model |
| + | - [ ] Implement click tracking |
| + | - [ ] Add geographic detection via CF headers |
| + | - [ ] Create user agent parsing |
| + | - [ ] Build referrer tracking |
| + | - [ ] Implement analytics aggregation |
| + | - [ ] Add batch write optimization |
| + | - [ ] Create analytics API endpoints |
| + | |
| + | ### Phase 6: Frontend Interface (Week 4-5) |
| + | - [ ] Set up frontend framework (React/Vue) |
| + | - [ ] Create authentication flow UI |
| + | - [ ] Build link management dashboard |
| + | - [ ] Implement QR code preview |
| + | - [ ] Add analytics visualization |
| + | - [ ] Create link creation form |
| + | - [ ] Build responsive mobile interface |
| + | - [ ] Add dark mode support |
| + | |
| + | ### Phase 7: Polish & Optimization (Week 5-6) |
| + | - [ ] Implement comprehensive error handling |
| + | - [ ] Add input validation across all endpoints |
| + | - [ ] Set up monitoring and alerting |
| + | - [ ] Optimize database queries |
| + | - [ ] Implement caching strategy |
| + | - [ ] Add security headers |
| + | - [ ] Create API documentation |
| + | - [ ] Write deployment scripts |
| + | |
| + | ## Files to Create/Modify |
| + | |
| + | ### Project Structure |
| + | ``` |
| + | / |
| + | โโโ wrangler.toml # Workers configuration |
| + | โโโ package.json # Dependencies |
| + | โโโ src/ |
| + | โ โโโ index.js # Main worker entry |
| + | โ โโโ routes/ |
| + | โ โ โโโ auth.js # Authentication endpoints |
| + | โ โ โโโ api.js # API routes |
| + | โ โ โโโ redirect.js # Redirect handler |
| + | โ โโโ lib/ |
| + | โ โ โโโ db.js # Database operations |
| + | โ โ โโโ qr.js # QR generation |
| + | โ โ โโโ email.js # Email service |
| + | โ โ โโโ analytics.js # Analytics tracking |
| + | โ โโโ middleware/ |
| + | โ โ โโโ auth.js # Auth middleware |
| + | โ โ โโโ rateLimit.js # Rate limiting |
| + | โ โโโ utils/ |
| + | โ โโโ slug.js # Slug generation |
| + | โ โโโ validation.js # Input validation |
| + | โโโ schema/ |
| + | โ โโโ schema.sql # D1 database schema |
| + | โโโ frontend/ |
| + | โ โโโ src/ # Frontend application |
| + | โ โโโ public/ # Static assets |
| + | โโโ tests/ |
| + | โโโ unit/ # Unit tests |
| + | โโโ integration/ # Integration tests |
| + | ``` |
| + | |
| + | ## Testing Strategy |
| + | |
| + | ### Unit Tests |
| + | - Slug generation uniqueness and format |
| + | - URL validation and sanitization |
| + | - Token generation and expiry |
| + | - QR code generation with various inputs |
| + | - Analytics data aggregation |
| + | |
| + | ### Integration Tests |
| + | - Full authentication flow |
| + | - URL creation โ redirect โ analytics |
| + | - QR generation with logo upload |
| + | - Rate limiting behavior |
| + | - Email delivery |
| + | |
| + | ### Performance Tests |
| + | - Redirect latency under load |
| + | - QR generation performance |
| + | - Database query optimization |
| + | - Concurrent user handling |
| + | |
| + | ## Edge Cases & Error Handling |
| + | - **Duplicate slugs**: Retry with different random slug or return error |
| + | - **Invalid URLs**: Validate protocol, prevent javascript: and data: URIs |
| + | - **Large logo files**: Resize before storage, reject if > 5MB |
| + | - **Email delivery failure**: Queue for retry, provide alternate auth method |
| + | - **Rate limit exceeded**: Return 429 with retry-after header |
| + | - **Database connection issues**: Implement circuit breaker pattern |
| + | - **QR generation timeout**: Pre-generate and cache popular QRs |
| + | |
| + | ## Security Considerations |
| + | - Input sanitization for all user inputs |
| + | - CSRF protection on state-changing operations |
| + | - Secure random token generation (crypto.getRandomValues) |
| + | - Content Security Policy headers |
| + | - SQL injection prevention via parameterized queries |
| + | - XSS prevention in frontend |
| + | - Rate limiting per IP and per user |
| + | - Secure cookie settings (httpOnly, secure, sameSite) |
| + | |
| + | ## Performance Targets |
| + | - Redirect latency: < 50ms globally |
| + | - QR generation: < 500ms with logo |
| + | - API response time: < 200ms |
| + | - Time to interactive (frontend): < 2s |
| + | - Database queries: < 10ms for reads |
| + | |
| + | ## Cost Estimation |
| + | - Workers: ~$5/month (10M requests) |
| + | - D1: Free tier (5GB, 500M reads) |
| + | - R2: Free tier (10GB storage) |
| + | - Email service: ~$10/month (Resend starter) |
| + | - **Total**: ~$15/month for moderate usage |
| + | |
| + | ## Open Questions |
| + | - [ ] Should we support custom domains for branded short links? |
| + | - [ ] How should we handle GDPR/privacy for analytics? |
| + | - [ ] Should QR codes support dynamic content updates? |
| + | - [ ] Do we need bulk import/export functionality? |
| + | - [ ] Should we add team/organization support later? |
| + | |
| + | ## Future Considerations |
| + | - Custom domain support for enterprise users |
| + | - API access for programmatic link creation |
| + | - Webhook support for analytics events |
| + | - A/B testing for destination URLs |
| + | - Link expiration and scheduling |
| + | - Password-protected links |
| + | - Browser extension for quick shortening |
| + | - Mobile app for QR scanning and management |
| + | |
| + | ## Documentation Plan |
| + | After implementation, create `dev/docs/20250822-qrurl-mvp.md` with: |
| + | - System architecture overview |
| + | - API documentation with examples |
| + | - Deployment guide |
| + | - Configuration options |
| + | - Troubleshooting common issues |
| + | - Performance tuning guide |
| + | - Security best practices |
| + | |
| + | ## Success Metrics |
| + | - Successfully deploy to production |
| + | - Handle 1000+ redirects/day without issues |
| + | - Generate QR codes with logos that scan reliably |
| + | - Achieve < 50ms redirect latency globally |
| + | - Pass security audit |
| + | - Complete implementation within 6 weeks |
| + | |
| + | ## Next Steps |
| + | 1. Review and approve this plan |
| + | 2. Set up Cloudflare account and services |
| + | 3. Initialize project repository |
| + | 4. Begin Phase 1 implementation |
| + | 5. Set up CI/CD pipeline |
| \ No newline at end of file | |
frontend/README.md
+5
-0
| @@ | @@ -0,0 +1,5 @@ |
| + | # Vue 3 + Vite |
| + | |
| + | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more. |
| + | |
| + | Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support). |
frontend/index.html
+13
-0
| @@ | @@ -0,0 +1,13 @@ |
| + | <!doctype html> |
| + | <html lang="en"> |
| + | <head> |
| + | <meta charset="UTF-8" /> |
| + | <link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
| + | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| + | <title>QRurl - Short Links & QR Codes</title> |
| + | </head> |
| + | <body> |
| + | <div id="app"></div> |
| + | <script type="module" src="/src/main.js"></script> |
| + | </body> |
| + | </html> |
frontend/package-lock.json
+3684
-0
| @@ | @@ -0,0 +1,3684 @@ |
| + | { |
| + | "name": "frontend", |
| + | "version": "0.0.0", |
| + | "lockfileVersion": 3, |
| + | "requires": true, |
| + | "packages": { |
| + | "": { |
| + | "name": "frontend", |
| + | "version": "0.0.0", |
| + | "dependencies": { |
| + | "@tailwindcss/forms": "^0.5.10", |
| + | "@vueuse/core": "^13.7.0", |
| + | "autoprefixer": "^10.4.21", |
| + | "axios": "^1.11.0", |
| + | "lucide-vue-next": "^0.541.0", |
| + | "pinia": "^3.0.3", |
| + | "postcss": "^8.5.6", |
| + | "qrcode": "^1.5.4", |
| + | "qrcode.js": "^0.0.1", |
| + | "tailwindcss": "^3.4.17", |
| + | "vue": "^3.5.18", |
| + | "vue-router": "^4.5.1" |
| + | }, |
| + | "devDependencies": { |
| + | "@vitejs/plugin-vue": "^6.0.1", |
| + | "vite": "^7.1.2" |
| + | } |
| + | }, |
| + | "node_modules/@alloc/quick-lru": { |
| + | "version": "5.2.0", |
| + | "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", |
| + | "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=10" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/sindresorhus" |
| + | } |
| + | }, |
| + | "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", |
| + | "engines": { |
| + | "node": ">=6.9.0" |
| + | } |
| + | }, |
| + | "node_modules/@babel/helper-validator-identifier": { |
| + | "version": "7.27.1", |
| + | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", |
| + | "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=6.9.0" |
| + | } |
| + | }, |
| + | "node_modules/@babel/parser": { |
| + | "version": "7.28.3", |
| + | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", |
| + | "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@babel/types": "^7.28.2" |
| + | }, |
| + | "bin": { |
| + | "parser": "bin/babel-parser.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=6.0.0" |
| + | } |
| + | }, |
| + | "node_modules/@babel/types": { |
| + | "version": "7.28.2", |
| + | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", |
| + | "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@babel/helper-string-parser": "^7.27.1", |
| + | "@babel/helper-validator-identifier": "^7.27.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=6.9.0" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/aix-ppc64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", |
| + | "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", |
| + | "cpu": [ |
| + | "ppc64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "aix" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-arm": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", |
| + | "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", |
| + | "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/darwin-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/darwin-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", |
| + | "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/freebsd-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/freebsd-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", |
| + | "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-arm": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", |
| + | "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-ia32": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", |
| + | "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-loong64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", |
| + | "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", |
| + | "cpu": [ |
| + | "loong64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-mips64el": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", |
| + | "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", |
| + | "cpu": [ |
| + | "mips64el" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-ppc64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", |
| + | "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", |
| + | "cpu": [ |
| + | "ppc64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-riscv64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", |
| + | "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", |
| + | "cpu": [ |
| + | "riscv64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-s390x": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", |
| + | "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", |
| + | "cpu": [ |
| + | "s390x" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", |
| + | "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/netbsd-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "netbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/netbsd-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", |
| + | "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "netbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/openbsd-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "openbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/openbsd-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", |
| + | "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "openbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/openharmony-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "openharmony" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/sunos-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", |
| + | "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "sunos" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-arm64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", |
| + | "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-ia32": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", |
| + | "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-x64": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", |
| + | "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui": { |
| + | "version": "8.0.2", |
| + | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", |
| + | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "string-width": "^5.1.2", |
| + | "string-width-cjs": "npm:string-width@^4.2.0", |
| + | "strip-ansi": "^7.0.1", |
| + | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", |
| + | "wrap-ansi": "^8.1.0", |
| + | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/ansi-regex": { |
| + | "version": "6.2.0", |
| + | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", |
| + | "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/ansi-regex?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/ansi-styles": { |
| + | "version": "6.2.1", |
| + | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", |
| + | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/ansi-styles?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/emoji-regex": { |
| + | "version": "9.2.2", |
| + | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", |
| + | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/string-width": { |
| + | "version": "5.1.2", |
| + | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", |
| + | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "eastasianwidth": "^0.2.0", |
| + | "emoji-regex": "^9.2.2", |
| + | "strip-ansi": "^7.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/sindresorhus" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/strip-ansi": { |
| + | "version": "7.1.0", |
| + | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", |
| + | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "ansi-regex": "^6.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/strip-ansi?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { |
| + | "version": "8.1.0", |
| + | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", |
| + | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "ansi-styles": "^6.1.0", |
| + | "string-width": "^5.0.1", |
| + | "strip-ansi": "^7.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/@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", |
| + | "dependencies": { |
| + | "@jridgewell/sourcemap-codec": "^1.5.0", |
| + | "@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", |
| + | "engines": { |
| + | "node": ">=6.0.0" |
| + | } |
| + | }, |
| + | "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" |
| + | }, |
| + | "node_modules/@jridgewell/trace-mapping": { |
| + | "version": "0.3.30", |
| + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", |
| + | "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", |
| + | "license": "MIT", |
| + | "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==", |
| + | "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==", |
| + | "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==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@nodelib/fs.scandir": "2.1.5", |
| + | "fastq": "^1.6.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 8" |
| + | } |
| + | }, |
| + | "node_modules/@pkgjs/parseargs": { |
| + | "version": "0.11.0", |
| + | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", |
| + | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "engines": { |
| + | "node": ">=14" |
| + | } |
| + | }, |
| + | "node_modules/@rolldown/pluginutils": { |
| + | "version": "1.0.0-beta.29", |
| + | "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", |
| + | "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@rollup/rollup-android-arm-eabi": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", |
| + | "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-android-arm64": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", |
| + | "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-darwin-arm64": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", |
| + | "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-darwin-x64": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", |
| + | "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-freebsd-arm64": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", |
| + | "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-freebsd-x64": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", |
| + | "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", |
| + | "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-arm-musleabihf": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", |
| + | "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-arm64-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-arm64-musl": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", |
| + | "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", |
| + | "cpu": [ |
| + | "loong64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-ppc64-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", |
| + | "cpu": [ |
| + | "ppc64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-riscv64-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", |
| + | "cpu": [ |
| + | "riscv64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-riscv64-musl": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", |
| + | "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", |
| + | "cpu": [ |
| + | "riscv64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-s390x-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", |
| + | "cpu": [ |
| + | "s390x" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-x64-gnu": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", |
| + | "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-linux-x64-musl": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", |
| + | "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-win32-arm64-msvc": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", |
| + | "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-win32-ia32-msvc": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", |
| + | "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ] |
| + | }, |
| + | "node_modules/@rollup/rollup-win32-x64-msvc": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", |
| + | "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ] |
| + | }, |
| + | "node_modules/@tailwindcss/forms": { |
| + | "version": "0.5.10", |
| + | "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", |
| + | "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "mini-svg-data-uri": "^1.2.3" |
| + | }, |
| + | "peerDependencies": { |
| + | "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" |
| + | } |
| + | }, |
| + | "node_modules/@types/estree": { |
| + | "version": "1.0.8", |
| + | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", |
| + | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@types/web-bluetooth": { |
| + | "version": "0.0.21", |
| + | "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", |
| + | "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@vitejs/plugin-vue": { |
| + | "version": "6.0.1", |
| + | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", |
| + | "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@rolldown/pluginutils": "1.0.0-beta.29" |
| + | }, |
| + | "engines": { |
| + | "node": "^20.19.0 || >=22.12.0" |
| + | }, |
| + | "peerDependencies": { |
| + | "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", |
| + | "vue": "^3.2.25" |
| + | } |
| + | }, |
| + | "node_modules/@vue/compiler-core": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz", |
| + | "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@babel/parser": "^7.28.3", |
| + | "@vue/shared": "3.5.19", |
| + | "entities": "^4.5.0", |
| + | "estree-walker": "^2.0.2", |
| + | "source-map-js": "^1.2.1" |
| + | } |
| + | }, |
| + | "node_modules/@vue/compiler-dom": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz", |
| + | "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/compiler-core": "3.5.19", |
| + | "@vue/shared": "3.5.19" |
| + | } |
| + | }, |
| + | "node_modules/@vue/compiler-sfc": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz", |
| + | "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@babel/parser": "^7.28.3", |
| + | "@vue/compiler-core": "3.5.19", |
| + | "@vue/compiler-dom": "3.5.19", |
| + | "@vue/compiler-ssr": "3.5.19", |
| + | "@vue/shared": "3.5.19", |
| + | "estree-walker": "^2.0.2", |
| + | "magic-string": "^0.30.17", |
| + | "postcss": "^8.5.6", |
| + | "source-map-js": "^1.2.1" |
| + | } |
| + | }, |
| + | "node_modules/@vue/compiler-ssr": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz", |
| + | "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/compiler-dom": "3.5.19", |
| + | "@vue/shared": "3.5.19" |
| + | } |
| + | }, |
| + | "node_modules/@vue/devtools-api": { |
| + | "version": "7.7.7", |
| + | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", |
| + | "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/devtools-kit": "^7.7.7" |
| + | } |
| + | }, |
| + | "node_modules/@vue/devtools-kit": { |
| + | "version": "7.7.7", |
| + | "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", |
| + | "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/devtools-shared": "^7.7.7", |
| + | "birpc": "^2.3.0", |
| + | "hookable": "^5.5.3", |
| + | "mitt": "^3.0.1", |
| + | "perfect-debounce": "^1.0.0", |
| + | "speakingurl": "^14.0.1", |
| + | "superjson": "^2.2.2" |
| + | } |
| + | }, |
| + | "node_modules/@vue/devtools-shared": { |
| + | "version": "7.7.7", |
| + | "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", |
| + | "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "rfdc": "^1.4.1" |
| + | } |
| + | }, |
| + | "node_modules/@vue/reactivity": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.19.tgz", |
| + | "integrity": "sha512-4bueZg2qs5MSsK2dQk3sssV0cfvxb/QZntTC8v7J448GLgmfPkQ+27aDjlt40+XFqOwUq5yRxK5uQh14Fc9eVA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/shared": "3.5.19" |
| + | } |
| + | }, |
| + | "node_modules/@vue/runtime-core": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.19.tgz", |
| + | "integrity": "sha512-TaooCr8Hge1sWjLSyhdubnuofs3shhzZGfyD11gFolZrny76drPwBVQj28/z/4+msSFb18tOIg6VVVgf9/IbIA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/reactivity": "3.5.19", |
| + | "@vue/shared": "3.5.19" |
| + | } |
| + | }, |
| + | "node_modules/@vue/runtime-dom": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.19.tgz", |
| + | "integrity": "sha512-qmahqeok6ztuUTmV8lqd7N9ymbBzctNF885n8gL3xdCC1u2RnM/coX16Via0AiONQXUoYpxPojL3U1IsDgSWUQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/reactivity": "3.5.19", |
| + | "@vue/runtime-core": "3.5.19", |
| + | "@vue/shared": "3.5.19", |
| + | "csstype": "^3.1.3" |
| + | } |
| + | }, |
| + | "node_modules/@vue/server-renderer": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.19.tgz", |
| + | "integrity": "sha512-ZJ/zV9SQuaIO+BEEVq/2a6fipyrSYfjKMU3267bPUk+oTx/hZq3RzV7VCh0Unlppt39Bvh6+NzxeopIFv4HJNg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/compiler-ssr": "3.5.19", |
| + | "@vue/shared": "3.5.19" |
| + | }, |
| + | "peerDependencies": { |
| + | "vue": "3.5.19" |
| + | } |
| + | }, |
| + | "node_modules/@vue/shared": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.19.tgz", |
| + | "integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@vueuse/core": { |
| + | "version": "13.7.0", |
| + | "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.7.0.tgz", |
| + | "integrity": "sha512-myagn09+c6BmS6yHc1gTwwsdZilAovHslMjyykmZH3JNyzI5HoWhv114IIdytXiPipdHJ2gDUx0PB93jRduJYg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@types/web-bluetooth": "^0.0.21", |
| + | "@vueuse/metadata": "13.7.0", |
| + | "@vueuse/shared": "13.7.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antfu" |
| + | }, |
| + | "peerDependencies": { |
| + | "vue": "^3.5.0" |
| + | } |
| + | }, |
| + | "node_modules/@vueuse/metadata": { |
| + | "version": "13.7.0", |
| + | "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.7.0.tgz", |
| + | "integrity": "sha512-8okFhS/1ite8EwUdZZfvTYowNTfXmVCOrBFlA31O0HD8HKXhY+WtTRyF0LwbpJfoFPc+s9anNJIXMVrvP7UTZg==", |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antfu" |
| + | } |
| + | }, |
| + | "node_modules/@vueuse/shared": { |
| + | "version": "13.7.0", |
| + | "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.7.0.tgz", |
| + | "integrity": "sha512-Wi2LpJi4UA9kM0OZ0FCZslACp92HlVNw1KPaDY6RAzvQ+J1s7seOtcOpmkfbD5aBSmMn9NvOakc8ZxMxmDXTIg==", |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antfu" |
| + | }, |
| + | "peerDependencies": { |
| + | "vue": "^3.5.0" |
| + | } |
| + | }, |
| + | "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/any-promise": { |
| + | "version": "1.3.0", |
| + | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", |
| + | "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", |
| + | "license": "MIT" |
| + | }, |
| + | "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", |
| + | "dependencies": { |
| + | "normalize-path": "^3.0.0", |
| + | "picomatch": "^2.0.4" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 8" |
| + | } |
| + | }, |
| + | "node_modules/anymatch/node_modules/picomatch": { |
| + | "version": "2.3.1", |
| + | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", |
| + | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=8.6" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/jonschlinkert" |
| + | } |
| + | }, |
| + | "node_modules/arg": { |
| + | "version": "5.0.2", |
| + | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", |
| + | "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/asynckit": { |
| + | "version": "0.4.0", |
| + | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", |
| + | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/autoprefixer": { |
| + | "version": "10.4.21", |
| + | "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", |
| + | "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/postcss/" |
| + | }, |
| + | { |
| + | "type": "tidelift", |
| + | "url": "https://tidelift.com/funding/github/npm/autoprefixer" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "browserslist": "^4.24.4", |
| + | "caniuse-lite": "^1.0.30001702", |
| + | "fraction.js": "^4.3.7", |
| + | "normalize-range": "^0.1.2", |
| + | "picocolors": "^1.1.1", |
| + | "postcss-value-parser": "^4.2.0" |
| + | }, |
| + | "bin": { |
| + | "autoprefixer": "bin/autoprefixer" |
| + | }, |
| + | "engines": { |
| + | "node": "^10 || ^12 || >=14" |
| + | }, |
| + | "peerDependencies": { |
| + | "postcss": "^8.1.0" |
| + | } |
| + | }, |
| + | "node_modules/axios": { |
| + | "version": "1.11.0", |
| + | "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", |
| + | "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "follow-redirects": "^1.15.6", |
| + | "form-data": "^4.0.4", |
| + | "proxy-from-env": "^1.1.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" |
| + | }, |
| + | "node_modules/binary-extensions": { |
| + | "version": "2.3.0", |
| + | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", |
| + | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=8" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/sindresorhus" |
| + | } |
| + | }, |
| + | "node_modules/birpc": { |
| + | "version": "2.5.0", |
| + | "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", |
| + | "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antfu" |
| + | } |
| + | }, |
| + | "node_modules/brace-expansion": { |
| + | "version": "2.0.2", |
| + | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", |
| + | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "balanced-match": "^1.0.0" |
| + | } |
| + | }, |
| + | "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.25.3", |
| + | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", |
| + | "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/browserslist" |
| + | }, |
| + | { |
| + | "type": "tidelift", |
| + | "url": "https://tidelift.com/funding/github/npm/browserslist" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "caniuse-lite": "^1.0.30001735", |
| + | "electron-to-chromium": "^1.5.204", |
| + | "node-releases": "^2.0.19", |
| + | "update-browserslist-db": "^1.1.3" |
| + | }, |
| + | "bin": { |
| + | "browserslist": "cli.js" |
| + | }, |
| + | "engines": { |
| + | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" |
| + | } |
| + | }, |
| + | "node_modules/call-bind-apply-helpers": { |
| + | "version": "1.0.2", |
| + | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", |
| + | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "es-errors": "^1.3.0", |
| + | "function-bind": "^1.1.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/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/camelcase-css": { |
| + | "version": "2.0.1", |
| + | "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", |
| + | "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/caniuse-lite": { |
| + | "version": "1.0.30001737", |
| + | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", |
| + | "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/browserslist" |
| + | }, |
| + | { |
| + | "type": "tidelift", |
| + | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "CC-BY-4.0" |
| + | }, |
| + | "node_modules/chokidar": { |
| + | "version": "3.6.0", |
| + | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", |
| + | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "anymatch": "~3.1.2", |
| + | "braces": "~3.0.2", |
| + | "glob-parent": "~5.1.2", |
| + | "is-binary-path": "~2.1.0", |
| + | "is-glob": "~4.0.1", |
| + | "normalize-path": "~3.0.0", |
| + | "readdirp": "~3.6.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 8.10.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://paulmillr.com/funding/" |
| + | }, |
| + | "optionalDependencies": { |
| + | "fsevents": "~2.3.2" |
| + | } |
| + | }, |
| + | "node_modules/chokidar/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==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "is-glob": "^4.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/cliui": { |
| + | "version": "6.0.0", |
| + | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", |
| + | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "string-width": "^4.2.0", |
| + | "strip-ansi": "^6.0.0", |
| + | "wrap-ansi": "^6.2.0" |
| + | } |
| + | }, |
| + | "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/combined-stream": { |
| + | "version": "1.0.8", |
| + | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", |
| + | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "delayed-stream": "~1.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.8" |
| + | } |
| + | }, |
| + | "node_modules/commander": { |
| + | "version": "4.1.1", |
| + | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", |
| + | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/copy-anything": { |
| + | "version": "3.0.5", |
| + | "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", |
| + | "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "is-what": "^4.1.8" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12.13" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/mesqueeb" |
| + | } |
| + | }, |
| + | "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/cssesc": { |
| + | "version": "3.0.0", |
| + | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", |
| + | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", |
| + | "license": "MIT", |
| + | "bin": { |
| + | "cssesc": "bin/cssesc" |
| + | }, |
| + | "engines": { |
| + | "node": ">=4" |
| + | } |
| + | }, |
| + | "node_modules/csstype": { |
| + | "version": "3.1.3", |
| + | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", |
| + | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", |
| + | "license": "MIT" |
| + | }, |
| + | "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==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/delayed-stream": { |
| + | "version": "1.0.0", |
| + | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", |
| + | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.4.0" |
| + | } |
| + | }, |
| + | "node_modules/detect-libc": { |
| + | "version": "2.0.4", |
| + | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", |
| + | "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">=8" |
| + | } |
| + | }, |
| + | "node_modules/didyoumean": { |
| + | "version": "1.2.2", |
| + | "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", |
| + | "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", |
| + | "license": "Apache-2.0" |
| + | }, |
| + | "node_modules/dijkstrajs": { |
| + | "version": "1.0.3", |
| + | "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", |
| + | "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/dlv": { |
| + | "version": "1.1.3", |
| + | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", |
| + | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/dunder-proto": { |
| + | "version": "1.0.1", |
| + | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", |
| + | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "call-bind-apply-helpers": "^1.0.1", |
| + | "es-errors": "^1.3.0", |
| + | "gopd": "^1.2.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/eastasianwidth": { |
| + | "version": "0.2.0", |
| + | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", |
| + | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/electron-to-chromium": { |
| + | "version": "1.5.208", |
| + | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", |
| + | "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", |
| + | "license": "ISC" |
| + | }, |
| + | "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/entities": { |
| + | "version": "4.5.0", |
| + | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", |
| + | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", |
| + | "license": "BSD-2-Clause", |
| + | "engines": { |
| + | "node": ">=0.12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/fb55/entities?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/es-define-property": { |
| + | "version": "1.0.1", |
| + | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", |
| + | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/es-errors": { |
| + | "version": "1.3.0", |
| + | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", |
| + | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/es-object-atoms": { |
| + | "version": "1.1.1", |
| + | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", |
| + | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "es-errors": "^1.3.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/es-set-tostringtag": { |
| + | "version": "2.1.0", |
| + | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", |
| + | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "es-errors": "^1.3.0", |
| + | "get-intrinsic": "^1.2.6", |
| + | "has-tostringtag": "^1.0.2", |
| + | "hasown": "^2.0.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/esbuild": { |
| + | "version": "0.25.9", |
| + | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", |
| + | "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", |
| + | "dev": true, |
| + | "hasInstallScript": true, |
| + | "license": "MIT", |
| + | "bin": { |
| + | "esbuild": "bin/esbuild" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@esbuild/aix-ppc64": "0.25.9", |
| + | "@esbuild/android-arm": "0.25.9", |
| + | "@esbuild/android-arm64": "0.25.9", |
| + | "@esbuild/android-x64": "0.25.9", |
| + | "@esbuild/darwin-arm64": "0.25.9", |
| + | "@esbuild/darwin-x64": "0.25.9", |
| + | "@esbuild/freebsd-arm64": "0.25.9", |
| + | "@esbuild/freebsd-x64": "0.25.9", |
| + | "@esbuild/linux-arm": "0.25.9", |
| + | "@esbuild/linux-arm64": "0.25.9", |
| + | "@esbuild/linux-ia32": "0.25.9", |
| + | "@esbuild/linux-loong64": "0.25.9", |
| + | "@esbuild/linux-mips64el": "0.25.9", |
| + | "@esbuild/linux-ppc64": "0.25.9", |
| + | "@esbuild/linux-riscv64": "0.25.9", |
| + | "@esbuild/linux-s390x": "0.25.9", |
| + | "@esbuild/linux-x64": "0.25.9", |
| + | "@esbuild/netbsd-arm64": "0.25.9", |
| + | "@esbuild/netbsd-x64": "0.25.9", |
| + | "@esbuild/openbsd-arm64": "0.25.9", |
| + | "@esbuild/openbsd-x64": "0.25.9", |
| + | "@esbuild/openharmony-arm64": "0.25.9", |
| + | "@esbuild/sunos-x64": "0.25.9", |
| + | "@esbuild/win32-arm64": "0.25.9", |
| + | "@esbuild/win32-ia32": "0.25.9", |
| + | "@esbuild/win32-x64": "0.25.9" |
| + | } |
| + | }, |
| + | "node_modules/escalade": { |
| + | "version": "3.2.0", |
| + | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", |
| + | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=6" |
| + | } |
| + | }, |
| + | "node_modules/estree-walker": { |
| + | "version": "2.0.2", |
| + | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", |
| + | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", |
| + | "license": "MIT" |
| + | }, |
| + | "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==", |
| + | "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-glob/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==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "is-glob": "^4.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/fastq": { |
| + | "version": "1.19.1", |
| + | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", |
| + | "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "reusify": "^1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/fdir": { |
| + | "version": "6.5.0", |
| + | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", |
| + | "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=12.0.0" |
| + | }, |
| + | "peerDependencies": { |
| + | "picomatch": "^3 || ^4" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "picomatch": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "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/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/follow-redirects": { |
| + | "version": "1.15.11", |
| + | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", |
| + | "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", |
| + | "funding": [ |
| + | { |
| + | "type": "individual", |
| + | "url": "https://github.com/sponsors/RubenVerborgh" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=4.0" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "debug": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/foreground-child": { |
| + | "version": "3.3.1", |
| + | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", |
| + | "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "cross-spawn": "^7.0.6", |
| + | "signal-exit": "^4.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=14" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | } |
| + | }, |
| + | "node_modules/form-data": { |
| + | "version": "4.0.4", |
| + | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", |
| + | "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "asynckit": "^0.4.0", |
| + | "combined-stream": "^1.0.8", |
| + | "es-set-tostringtag": "^2.1.0", |
| + | "hasown": "^2.0.2", |
| + | "mime-types": "^2.1.12" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/fraction.js": { |
| + | "version": "4.3.7", |
| + | "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", |
| + | "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": "*" |
| + | }, |
| + | "funding": { |
| + | "type": "patreon", |
| + | "url": "https://github.com/sponsors/rawify" |
| + | } |
| + | }, |
| + | "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" |
| + | ], |
| + | "engines": { |
| + | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
| + | } |
| + | }, |
| + | "node_modules/function-bind": { |
| + | "version": "1.1.2", |
| + | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", |
| + | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/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==", |
| + | "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-proto": { |
| + | "version": "1.0.1", |
| + | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", |
| + | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "dunder-proto": "^1.0.1", |
| + | "es-object-atoms": "^1.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/glob": { |
| + | "version": "10.4.5", |
| + | "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", |
| + | "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "foreground-child": "^3.1.0", |
| + | "jackspeak": "^3.1.2", |
| + | "minimatch": "^9.0.4", |
| + | "minipass": "^7.1.2", |
| + | "package-json-from-dist": "^1.0.0", |
| + | "path-scurry": "^1.11.1" |
| + | }, |
| + | "bin": { |
| + | "glob": "dist/esm/bin.mjs" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | } |
| + | }, |
| + | "node_modules/glob-parent": { |
| + | "version": "6.0.2", |
| + | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", |
| + | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "is-glob": "^4.0.3" |
| + | }, |
| + | "engines": { |
| + | "node": ">=10.13.0" |
| + | } |
| + | }, |
| + | "node_modules/gopd": { |
| + | "version": "1.2.0", |
| + | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", |
| + | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/has-symbols": { |
| + | "version": "1.1.0", |
| + | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", |
| + | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/has-tostringtag": { |
| + | "version": "1.0.2", |
| + | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", |
| + | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "has-symbols": "^1.0.3" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/hasown": { |
| + | "version": "2.0.2", |
| + | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", |
| + | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "function-bind": "^1.1.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/hookable": { |
| + | "version": "5.5.3", |
| + | "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", |
| + | "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/is-binary-path": { |
| + | "version": "2.1.0", |
| + | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", |
| + | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "binary-extensions": "^2.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=8" |
| + | } |
| + | }, |
| + | "node_modules/is-core-module": { |
| + | "version": "2.16.1", |
| + | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", |
| + | "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "hasown": "^2.0.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/is-extglob": { |
| + | "version": "2.1.1", |
| + | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", |
| + | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", |
| + | "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==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "is-extglob": "^2.1.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "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-what": { |
| + | "version": "4.1.16", |
| + | "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", |
| + | "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=12.13" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/mesqueeb" |
| + | } |
| + | }, |
| + | "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/jackspeak": { |
| + | "version": "3.4.3", |
| + | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", |
| + | "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", |
| + | "license": "BlueOak-1.0.0", |
| + | "dependencies": { |
| + | "@isaacs/cliui": "^8.0.2" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@pkgjs/parseargs": "^0.11.0" |
| + | } |
| + | }, |
| + | "node_modules/jiti": { |
| + | "version": "2.5.1", |
| + | "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", |
| + | "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "peer": true, |
| + | "bin": { |
| + | "jiti": "lib/jiti-cli.mjs" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", |
| + | "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "peer": true, |
| + | "dependencies": { |
| + | "detect-libc": "^2.0.3" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | }, |
| + | "optionalDependencies": { |
| + | "lightningcss-darwin-arm64": "1.30.1", |
| + | "lightningcss-darwin-x64": "1.30.1", |
| + | "lightningcss-freebsd-x64": "1.30.1", |
| + | "lightningcss-linux-arm-gnueabihf": "1.30.1", |
| + | "lightningcss-linux-arm64-gnu": "1.30.1", |
| + | "lightningcss-linux-arm64-musl": "1.30.1", |
| + | "lightningcss-linux-x64-gnu": "1.30.1", |
| + | "lightningcss-linux-x64-musl": "1.30.1", |
| + | "lightningcss-win32-arm64-msvc": "1.30.1", |
| + | "lightningcss-win32-x64-msvc": "1.30.1" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-darwin-arm64": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", |
| + | "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-darwin-x64": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", |
| + | "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-freebsd-x64": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", |
| + | "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-linux-arm-gnueabihf": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", |
| + | "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-linux-arm64-gnu": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", |
| + | "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-linux-arm64-musl": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", |
| + | "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-linux-x64-gnu": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", |
| + | "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-linux-x64-musl": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", |
| + | "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-win32-arm64-msvc": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", |
| + | "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lightningcss-win32-x64-msvc": { |
| + | "version": "1.30.1", |
| + | "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", |
| + | "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MPL-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "peer": true, |
| + | "engines": { |
| + | "node": ">= 12.0.0" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/parcel" |
| + | } |
| + | }, |
| + | "node_modules/lilconfig": { |
| + | "version": "3.1.3", |
| + | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", |
| + | "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=14" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antonk52" |
| + | } |
| + | }, |
| + | "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==", |
| + | "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/lru-cache": { |
| + | "version": "10.4.3", |
| + | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", |
| + | "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", |
| + | "license": "ISC" |
| + | }, |
| + | "node_modules/lucide-vue-next": { |
| + | "version": "0.541.0", |
| + | "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.541.0.tgz", |
| + | "integrity": "sha512-BXY//i7H0ojCDRmux7WzhTl2FiKVmE42fyaLuQOKBGaeBRLEGkkSgYMBxIk9ZjAKa+JELRmFVV1xAFUumB89QA==", |
| + | "license": "ISC", |
| + | "peerDependencies": { |
| + | "vue": ">=3.0.1" |
| + | } |
| + | }, |
| + | "node_modules/magic-string": { |
| + | "version": "0.30.18", |
| + | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", |
| + | "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@jridgewell/sourcemap-codec": "^1.5.5" |
| + | } |
| + | }, |
| + | "node_modules/math-intrinsics": { |
| + | "version": "1.1.0", |
| + | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
| + | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | } |
| + | }, |
| + | "node_modules/merge2": { |
| + | "version": "1.4.1", |
| + | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", |
| + | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 8" |
| + | } |
| + | }, |
| + | "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/micromatch/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/mime-db": { |
| + | "version": "1.52.0", |
| + | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
| + | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.6" |
| + | } |
| + | }, |
| + | "node_modules/mime-types": { |
| + | "version": "2.1.35", |
| + | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
| + | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "mime-db": "1.52.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.6" |
| + | } |
| + | }, |
| + | "node_modules/mini-svg-data-uri": { |
| + | "version": "1.4.4", |
| + | "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", |
| + | "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", |
| + | "license": "MIT", |
| + | "bin": { |
| + | "mini-svg-data-uri": "cli.js" |
| + | } |
| + | }, |
| + | "node_modules/minimatch": { |
| + | "version": "9.0.5", |
| + | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", |
| + | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "brace-expansion": "^2.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=16 || 14 >=14.17" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | } |
| + | }, |
| + | "node_modules/minipass": { |
| + | "version": "7.1.2", |
| + | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", |
| + | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", |
| + | "license": "ISC", |
| + | "engines": { |
| + | "node": ">=16 || 14 >=14.17" |
| + | } |
| + | }, |
| + | "node_modules/mitt": { |
| + | "version": "3.0.1", |
| + | "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", |
| + | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/mz": { |
| + | "version": "2.7.0", |
| + | "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", |
| + | "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "any-promise": "^1.0.0", |
| + | "object-assign": "^4.0.1", |
| + | "thenify-all": "^1.0.0" |
| + | } |
| + | }, |
| + | "node_modules/nanoid": { |
| + | "version": "3.3.11", |
| + | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", |
| + | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", |
| + | "funding": [ |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "bin": { |
| + | "nanoid": "bin/nanoid.cjs" |
| + | }, |
| + | "engines": { |
| + | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" |
| + | } |
| + | }, |
| + | "node_modules/node-releases": { |
| + | "version": "2.0.19", |
| + | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", |
| + | "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/normalize-path": { |
| + | "version": "3.0.0", |
| + | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", |
| + | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/normalize-range": { |
| + | "version": "0.1.2", |
| + | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", |
| + | "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/object-assign": { |
| + | "version": "4.1.1", |
| + | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", |
| + | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/object-hash": { |
| + | "version": "3.0.0", |
| + | "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", |
| + | "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "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/package-json-from-dist": { |
| + | "version": "1.0.1", |
| + | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", |
| + | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", |
| + | "license": "BlueOak-1.0.0" |
| + | }, |
| + | "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-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/path-parse": { |
| + | "version": "1.0.7", |
| + | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", |
| + | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/path-scurry": { |
| + | "version": "1.11.1", |
| + | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", |
| + | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", |
| + | "license": "BlueOak-1.0.0", |
| + | "dependencies": { |
| + | "lru-cache": "^10.2.0", |
| + | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=16 || 14 >=14.18" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | } |
| + | }, |
| + | "node_modules/perfect-debounce": { |
| + | "version": "1.0.0", |
| + | "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", |
| + | "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", |
| + | "license": "MIT" |
| + | }, |
| + | "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": "4.0.3", |
| + | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", |
| + | "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=12" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/jonschlinkert" |
| + | } |
| + | }, |
| + | "node_modules/pify": { |
| + | "version": "2.3.0", |
| + | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", |
| + | "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/pinia": { |
| + | "version": "3.0.3", |
| + | "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", |
| + | "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/devtools-api": "^7.7.2" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/posva" |
| + | }, |
| + | "peerDependencies": { |
| + | "typescript": ">=4.4.4", |
| + | "vue": "^2.7.0 || ^3.5.11" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "typescript": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "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", |
| + | "engines": { |
| + | "node": ">= 6" |
| + | } |
| + | }, |
| + | "node_modules/pngjs": { |
| + | "version": "5.0.0", |
| + | "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", |
| + | "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=10.13.0" |
| + | } |
| + | }, |
| + | "node_modules/postcss": { |
| + | "version": "8.5.6", |
| + | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", |
| + | "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/postcss/" |
| + | }, |
| + | { |
| + | "type": "tidelift", |
| + | "url": "https://tidelift.com/funding/github/npm/postcss" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "nanoid": "^3.3.11", |
| + | "picocolors": "^1.1.1", |
| + | "source-map-js": "^1.2.1" |
| + | }, |
| + | "engines": { |
| + | "node": "^10 || ^12 || >=14" |
| + | } |
| + | }, |
| + | "node_modules/postcss-import": { |
| + | "version": "15.1.0", |
| + | "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", |
| + | "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "postcss-value-parser": "^4.0.0", |
| + | "read-cache": "^1.0.0", |
| + | "resolve": "^1.1.7" |
| + | }, |
| + | "engines": { |
| + | "node": ">=14.0.0" |
| + | }, |
| + | "peerDependencies": { |
| + | "postcss": "^8.0.0" |
| + | } |
| + | }, |
| + | "node_modules/postcss-js": { |
| + | "version": "4.0.1", |
| + | "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", |
| + | "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "camelcase-css": "^2.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": "^12 || ^14 || >= 16" |
| + | }, |
| + | "funding": { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/postcss/" |
| + | }, |
| + | "peerDependencies": { |
| + | "postcss": "^8.4.21" |
| + | } |
| + | }, |
| + | "node_modules/postcss-load-config": { |
| + | "version": "4.0.2", |
| + | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", |
| + | "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/postcss/" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "lilconfig": "^3.0.0", |
| + | "yaml": "^2.3.4" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 14" |
| + | }, |
| + | "peerDependencies": { |
| + | "postcss": ">=8.0.9", |
| + | "ts-node": ">=9.0.0" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "postcss": { |
| + | "optional": true |
| + | }, |
| + | "ts-node": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/postcss-nested": { |
| + | "version": "6.2.0", |
| + | "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", |
| + | "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/postcss/" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "postcss-selector-parser": "^6.1.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12.0" |
| + | }, |
| + | "peerDependencies": { |
| + | "postcss": "^8.2.14" |
| + | } |
| + | }, |
| + | "node_modules/postcss-selector-parser": { |
| + | "version": "6.1.2", |
| + | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", |
| + | "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "cssesc": "^3.0.0", |
| + | "util-deprecate": "^1.0.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">=4" |
| + | } |
| + | }, |
| + | "node_modules/postcss-value-parser": { |
| + | "version": "4.2.0", |
| + | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", |
| + | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/proxy-from-env": { |
| + | "version": "1.1.0", |
| + | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", |
| + | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/qrcode": { |
| + | "version": "1.5.4", |
| + | "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", |
| + | "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "dijkstrajs": "^1.0.1", |
| + | "pngjs": "^5.0.0", |
| + | "yargs": "^15.3.1" |
| + | }, |
| + | "bin": { |
| + | "qrcode": "bin/qrcode" |
| + | }, |
| + | "engines": { |
| + | "node": ">=10.13.0" |
| + | } |
| + | }, |
| + | "node_modules/qrcode.js": { |
| + | "version": "0.0.1", |
| + | "resolved": "https://registry.npmjs.org/qrcode.js/-/qrcode.js-0.0.1.tgz", |
| + | "integrity": "sha512-EpHuKYzjYH/+SQtAlo4NYe3wpsYzKadKa154Ch163K+jrUlz6pVInWgJlsj5d1dxngYEjuqnClfzutL8Z9rqcg==", |
| + | "license": "Apache" |
| + | }, |
| + | "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==", |
| + | "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/read-cache": { |
| + | "version": "1.0.0", |
| + | "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", |
| + | "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "pify": "^2.3.0" |
| + | } |
| + | }, |
| + | "node_modules/readdirp": { |
| + | "version": "3.6.0", |
| + | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", |
| + | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "picomatch": "^2.2.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=8.10.0" |
| + | } |
| + | }, |
| + | "node_modules/readdirp/node_modules/picomatch": { |
| + | "version": "2.3.1", |
| + | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", |
| + | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=8.6" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/jonschlinkert" |
| + | } |
| + | }, |
| + | "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==", |
| + | "license": "ISC" |
| + | }, |
| + | "node_modules/resolve": { |
| + | "version": "1.22.10", |
| + | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", |
| + | "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "is-core-module": "^2.16.0", |
| + | "path-parse": "^1.0.7", |
| + | "supports-preserve-symlinks-flag": "^1.0.0" |
| + | }, |
| + | "bin": { |
| + | "resolve": "bin/resolve" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/reusify": { |
| + | "version": "1.1.0", |
| + | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", |
| + | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "iojs": ">=1.0.0", |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/rfdc": { |
| + | "version": "1.4.1", |
| + | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", |
| + | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/rollup": { |
| + | "version": "4.47.1", |
| + | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", |
| + | "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@types/estree": "1.0.8" |
| + | }, |
| + | "bin": { |
| + | "rollup": "dist/bin/rollup" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18.0.0", |
| + | "npm": ">=8.0.0" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@rollup/rollup-android-arm-eabi": "4.47.1", |
| + | "@rollup/rollup-android-arm64": "4.47.1", |
| + | "@rollup/rollup-darwin-arm64": "4.47.1", |
| + | "@rollup/rollup-darwin-x64": "4.47.1", |
| + | "@rollup/rollup-freebsd-arm64": "4.47.1", |
| + | "@rollup/rollup-freebsd-x64": "4.47.1", |
| + | "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", |
| + | "@rollup/rollup-linux-arm-musleabihf": "4.47.1", |
| + | "@rollup/rollup-linux-arm64-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-arm64-musl": "4.47.1", |
| + | "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-ppc64-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-riscv64-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-riscv64-musl": "4.47.1", |
| + | "@rollup/rollup-linux-s390x-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-x64-gnu": "4.47.1", |
| + | "@rollup/rollup-linux-x64-musl": "4.47.1", |
| + | "@rollup/rollup-win32-arm64-msvc": "4.47.1", |
| + | "@rollup/rollup-win32-ia32-msvc": "4.47.1", |
| + | "@rollup/rollup-win32-x64-msvc": "4.47.1", |
| + | "fsevents": "~2.3.2" |
| + | } |
| + | }, |
| + | "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==", |
| + | "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/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==", |
| + | "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/signal-exit": { |
| + | "version": "4.1.0", |
| + | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", |
| + | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", |
| + | "license": "ISC", |
| + | "engines": { |
| + | "node": ">=14" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/isaacs" |
| + | } |
| + | }, |
| + | "node_modules/source-map-js": { |
| + | "version": "1.2.1", |
| + | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", |
| + | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", |
| + | "license": "BSD-3-Clause", |
| + | "engines": { |
| + | "node": ">=0.10.0" |
| + | } |
| + | }, |
| + | "node_modules/speakingurl": { |
| + | "version": "14.0.1", |
| + | "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", |
| + | "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", |
| + | "license": "BSD-3-Clause", |
| + | "engines": { |
| + | "node": ">=0.10.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/string-width-cjs": { |
| + | "name": "string-width", |
| + | "version": "4.2.3", |
| + | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| + | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| + | "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-ansi-cjs": { |
| + | "name": "strip-ansi", |
| + | "version": "6.0.1", |
| + | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| + | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "ansi-regex": "^5.0.1" |
| + | }, |
| + | "engines": { |
| + | "node": ">=8" |
| + | } |
| + | }, |
| + | "node_modules/sucrase": { |
| + | "version": "3.35.0", |
| + | "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", |
| + | "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@jridgewell/gen-mapping": "^0.3.2", |
| + | "commander": "^4.0.0", |
| + | "glob": "^10.3.10", |
| + | "lines-and-columns": "^1.1.6", |
| + | "mz": "^2.7.0", |
| + | "pirates": "^4.0.1", |
| + | "ts-interface-checker": "^0.1.9" |
| + | }, |
| + | "bin": { |
| + | "sucrase": "bin/sucrase", |
| + | "sucrase-node": "bin/sucrase-node" |
| + | }, |
| + | "engines": { |
| + | "node": ">=16 || 14 >=14.17" |
| + | } |
| + | }, |
| + | "node_modules/superjson": { |
| + | "version": "2.2.2", |
| + | "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", |
| + | "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "copy-anything": "^3.0.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/supports-preserve-symlinks-flag": { |
| + | "version": "1.0.0", |
| + | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", |
| + | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">= 0.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/ljharb" |
| + | } |
| + | }, |
| + | "node_modules/tailwindcss": { |
| + | "version": "3.4.17", |
| + | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", |
| + | "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@alloc/quick-lru": "^5.2.0", |
| + | "arg": "^5.0.2", |
| + | "chokidar": "^3.6.0", |
| + | "didyoumean": "^1.2.2", |
| + | "dlv": "^1.1.3", |
| + | "fast-glob": "^3.3.2", |
| + | "glob-parent": "^6.0.2", |
| + | "is-glob": "^4.0.3", |
| + | "jiti": "^1.21.6", |
| + | "lilconfig": "^3.1.3", |
| + | "micromatch": "^4.0.8", |
| + | "normalize-path": "^3.0.0", |
| + | "object-hash": "^3.0.0", |
| + | "picocolors": "^1.1.1", |
| + | "postcss": "^8.4.47", |
| + | "postcss-import": "^15.1.0", |
| + | "postcss-js": "^4.0.1", |
| + | "postcss-load-config": "^4.0.2", |
| + | "postcss-nested": "^6.2.0", |
| + | "postcss-selector-parser": "^6.1.2", |
| + | "resolve": "^1.22.8", |
| + | "sucrase": "^3.35.0" |
| + | }, |
| + | "bin": { |
| + | "tailwind": "lib/cli.js", |
| + | "tailwindcss": "lib/cli.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=14.0.0" |
| + | } |
| + | }, |
| + | "node_modules/tailwindcss/node_modules/jiti": { |
| + | "version": "1.21.7", |
| + | "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", |
| + | "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", |
| + | "license": "MIT", |
| + | "bin": { |
| + | "jiti": "bin/jiti.js" |
| + | } |
| + | }, |
| + | "node_modules/thenify": { |
| + | "version": "3.3.1", |
| + | "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", |
| + | "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "any-promise": "^1.0.0" |
| + | } |
| + | }, |
| + | "node_modules/thenify-all": { |
| + | "version": "1.6.0", |
| + | "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", |
| + | "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "thenify": ">= 3.1.0 < 4" |
| + | }, |
| + | "engines": { |
| + | "node": ">=0.8" |
| + | } |
| + | }, |
| + | "node_modules/tinyglobby": { |
| + | "version": "0.2.14", |
| + | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", |
| + | "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "fdir": "^6.4.4", |
| + | "picomatch": "^4.0.2" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/SuperchupuDev" |
| + | } |
| + | }, |
| + | "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/ts-interface-checker": { |
| + | "version": "0.1.13", |
| + | "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", |
| + | "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", |
| + | "license": "Apache-2.0" |
| + | }, |
| + | "node_modules/update-browserslist-db": { |
| + | "version": "1.1.3", |
| + | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", |
| + | "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", |
| + | "funding": [ |
| + | { |
| + | "type": "opencollective", |
| + | "url": "https://opencollective.com/browserslist" |
| + | }, |
| + | { |
| + | "type": "tidelift", |
| + | "url": "https://tidelift.com/funding/github/npm/browserslist" |
| + | }, |
| + | { |
| + | "type": "github", |
| + | "url": "https://github.com/sponsors/ai" |
| + | } |
| + | ], |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "escalade": "^3.2.0", |
| + | "picocolors": "^1.1.1" |
| + | }, |
| + | "bin": { |
| + | "update-browserslist-db": "cli.js" |
| + | }, |
| + | "peerDependencies": { |
| + | "browserslist": ">= 4.21.0" |
| + | } |
| + | }, |
| + | "node_modules/util-deprecate": { |
| + | "version": "1.0.2", |
| + | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", |
| + | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/vite": { |
| + | "version": "7.1.3", |
| + | "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", |
| + | "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "esbuild": "^0.25.0", |
| + | "fdir": "^6.5.0", |
| + | "picomatch": "^4.0.3", |
| + | "postcss": "^8.5.6", |
| + | "rollup": "^4.43.0", |
| + | "tinyglobby": "^0.2.14" |
| + | }, |
| + | "bin": { |
| + | "vite": "bin/vite.js" |
| + | }, |
| + | "engines": { |
| + | "node": "^20.19.0 || >=22.12.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/vitejs/vite?sponsor=1" |
| + | }, |
| + | "optionalDependencies": { |
| + | "fsevents": "~2.3.3" |
| + | }, |
| + | "peerDependencies": { |
| + | "@types/node": "^20.19.0 || >=22.12.0", |
| + | "jiti": ">=1.21.0", |
| + | "less": "^4.0.0", |
| + | "lightningcss": "^1.21.0", |
| + | "sass": "^1.70.0", |
| + | "sass-embedded": "^1.70.0", |
| + | "stylus": ">=0.54.8", |
| + | "sugarss": "^5.0.0", |
| + | "terser": "^5.16.0", |
| + | "tsx": "^4.8.1", |
| + | "yaml": "^2.4.2" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "@types/node": { |
| + | "optional": true |
| + | }, |
| + | "jiti": { |
| + | "optional": true |
| + | }, |
| + | "less": { |
| + | "optional": true |
| + | }, |
| + | "lightningcss": { |
| + | "optional": true |
| + | }, |
| + | "sass": { |
| + | "optional": true |
| + | }, |
| + | "sass-embedded": { |
| + | "optional": true |
| + | }, |
| + | "stylus": { |
| + | "optional": true |
| + | }, |
| + | "sugarss": { |
| + | "optional": true |
| + | }, |
| + | "terser": { |
| + | "optional": true |
| + | }, |
| + | "tsx": { |
| + | "optional": true |
| + | }, |
| + | "yaml": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/vue": { |
| + | "version": "3.5.19", |
| + | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.19.tgz", |
| + | "integrity": "sha512-ZRh0HTmw6KChRYWgN8Ox/wi7VhpuGlvMPrHjIsdRbzKNgECFLzy+dKL5z9yGaBSjCpmcfJCbh3I1tNSRmBz2tg==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/compiler-dom": "3.5.19", |
| + | "@vue/compiler-sfc": "3.5.19", |
| + | "@vue/runtime-dom": "3.5.19", |
| + | "@vue/server-renderer": "3.5.19", |
| + | "@vue/shared": "3.5.19" |
| + | }, |
| + | "peerDependencies": { |
| + | "typescript": "*" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "typescript": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/vue-router": { |
| + | "version": "4.5.1", |
| + | "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", |
| + | "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@vue/devtools-api": "^6.6.4" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/posva" |
| + | }, |
| + | "peerDependencies": { |
| + | "vue": "^3.2.0" |
| + | } |
| + | }, |
| + | "node_modules/vue-router/node_modules/@vue/devtools-api": { |
| + | "version": "6.6.4", |
| + | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", |
| + | "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", |
| + | "license": "MIT" |
| + | }, |
| + | "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==", |
| + | "license": "ISC" |
| + | }, |
| + | "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==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "ansi-styles": "^4.0.0", |
| + | "string-width": "^4.1.0", |
| + | "strip-ansi": "^6.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=8" |
| + | } |
| + | }, |
| + | "node_modules/wrap-ansi-cjs": { |
| + | "name": "wrap-ansi", |
| + | "version": "7.0.0", |
| + | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", |
| + | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "ansi-styles": "^4.0.0", |
| + | "string-width": "^4.1.0", |
| + | "strip-ansi": "^6.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=10" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/y18n": { |
| + | "version": "4.0.3", |
| + | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", |
| + | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", |
| + | "license": "ISC" |
| + | }, |
| + | "node_modules/yaml": { |
| + | "version": "2.8.1", |
| + | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", |
| + | "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", |
| + | "license": "ISC", |
| + | "bin": { |
| + | "yaml": "bin.mjs" |
| + | }, |
| + | "engines": { |
| + | "node": ">= 14.6" |
| + | } |
| + | }, |
| + | "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==", |
| + | "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/yargs-parser": { |
| + | "version": "18.1.3", |
| + | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", |
| + | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", |
| + | "license": "ISC", |
| + | "dependencies": { |
| + | "camelcase": "^5.0.0", |
| + | "decamelize": "^1.2.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=6" |
| + | } |
| + | } |
| + | } |
| + | } |
frontend/package.json
+29
-0
| @@ | @@ -0,0 +1,29 @@ |
| + | { |
| + | "name": "frontend", |
| + | "private": true, |
| + | "version": "0.0.0", |
| + | "type": "module", |
| + | "scripts": { |
| + | "dev": "vite", |
| + | "build": "vite build", |
| + | "preview": "vite preview" |
| + | }, |
| + | "dependencies": { |
| + | "@tailwindcss/forms": "^0.5.10", |
| + | "@vueuse/core": "^13.7.0", |
| + | "autoprefixer": "^10.4.21", |
| + | "axios": "^1.11.0", |
| + | "lucide-vue-next": "^0.541.0", |
| + | "pinia": "^3.0.3", |
| + | "postcss": "^8.5.6", |
| + | "qrcode": "^1.5.4", |
| + | "qrcode.js": "^0.0.1", |
| + | "tailwindcss": "^3.4.17", |
| + | "vue": "^3.5.18", |
| + | "vue-router": "^4.5.1" |
| + | }, |
| + | "devDependencies": { |
| + | "@vitejs/plugin-vue": "^6.0.1", |
| + | "vite": "^7.1.2" |
| + | } |
| + | } |
frontend/postcss.config.js
+6
-0
| @@ | @@ -0,0 +1,6 @@ |
| + | export default { |
| + | plugins: { |
| + | tailwindcss: {}, |
| + | autoprefixer: {}, |
| + | }, |
| + | } |
| \ No newline at end of file | |
frontend/public/vite.svg
+1
-0
| @@ | @@ -0,0 +1 @@ |
| + | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> |
| \ No newline at end of file | |
frontend/src/App.vue
+19
-0
| @@ | @@ -0,0 +1,19 @@ |
| + | <template> |
| + | <div id="app" class="min-h-screen bg-gray-50"> |
| + | <router-view /> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { onMounted } from 'vue' |
| + | import { useAuthStore } from './stores/auth' |
| + | |
| + | const authStore = useAuthStore() |
| + | |
| + | onMounted(() => { |
| + | // Check if user is authenticated on app load |
| + | if (authStore.isAuthenticated) { |
| + | console.log('User authenticated:', authStore.user) |
| + | } |
| + | }) |
| + | </script> |
| \ No newline at end of file | |
frontend/src/components/QRCodeModal.vue
+109
-0
| @@ | @@ -0,0 +1,109 @@ |
| + | <template> |
| + | <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" @click.self="$emit('close')"> |
| + | <div class="bg-white rounded-2xl shadow-xl max-w-md w-full p-6"> |
| + | <div class="flex justify-between items-start mb-4"> |
| + | <h3 class="text-xl font-semibold text-gray-900">QR Code</h3> |
| + | <button |
| + | @click="$emit('close')" |
| + | class="text-gray-400 hover:text-gray-600 transition" |
| + | > |
| + | <XIcon class="h-6 w-6" /> |
| + | </button> |
| + | </div> |
| + | |
| + | <div class="space-y-4"> |
| + | <div class="bg-gray-50 rounded-lg p-4"> |
| + | <p class="text-sm text-gray-600 mb-2">{{ entry.name }}</p> |
| + | <p class="text-xs text-gray-500">{{ shortUrl }}</p> |
| + | </div> |
| + | |
| + | <div class="flex justify-center"> |
| + | <div v-if="loading" class="py-8"> |
| + | <Loader2Icon class="h-8 w-8 text-gray-400 animate-spin" /> |
| + | </div> |
| + | <img |
| + | v-else |
| + | :src="qrCodeUrl" |
| + | alt="QR Code" |
| + | class="w-64 h-64" |
| + | /> |
| + | </div> |
| + | |
| + | <div class="flex space-x-3"> |
| + | <button |
| + | @click="downloadQR" |
| + | class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-lg font-semibold hover:bg-indigo-700 transition flex items-center justify-center" |
| + | > |
| + | <DownloadIcon class="h-4 w-4 mr-2" /> |
| + | Download |
| + | </button> |
| + | <button |
| + | @click="copyQR" |
| + | class="flex-1 bg-gray-100 text-gray-700 py-2 px-4 rounded-lg font-semibold hover:bg-gray-200 transition flex items-center justify-center" |
| + | > |
| + | <CopyIcon class="h-4 w-4 mr-2" /> |
| + | Copy |
| + | </button> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { ref, computed, onMounted } from 'vue' |
| + | import { X as XIcon, Loader2 as Loader2Icon, Download as DownloadIcon, Copy as CopyIcon } from 'lucide-vue-next' |
| + | import { generateQRCodeWithLogo } from '../services/qrcode' |
| + | |
| + | const props = defineProps({ |
| + | entry: { |
| + | type: Object, |
| + | required: true |
| + | } |
| + | }) |
| + | |
| + | const emit = defineEmits(['close']) |
| + | |
| + | const loading = ref(true) |
| + | const qrCodeUrl = ref('') |
| + | |
| + | const shortUrl = computed(() => { |
| + | return `${import.meta.env.VITE_SHORT_URL || 'http://localhost:8787'}/${props.entry.slug}` |
| + | }) |
| + | |
| + | onMounted(async () => { |
| + | try { |
| + | qrCodeUrl.value = await generateQRCodeWithLogo(shortUrl.value, props.entry.logo_url) |
| + | } catch (error) { |
| + | console.error('Failed to generate QR code:', error) |
| + | // Try without logo as fallback |
| + | const { default: QRCode } = await import('qrcode') |
| + | qrCodeUrl.value = await QRCode.toDataURL(shortUrl.value, { |
| + | errorCorrectionLevel: 'H', |
| + | width: 512, |
| + | margin: 2 |
| + | }) |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | }) |
| + | |
| + | function downloadQR() { |
| + | const link = document.createElement('a') |
| + | link.download = `qr-${props.entry.slug}.png` |
| + | link.href = qrCodeUrl.value |
| + | link.click() |
| + | } |
| + | |
| + | async function copyQR() { |
| + | try { |
| + | const blob = await fetch(qrCodeUrl.value).then(r => r.blob()) |
| + | await navigator.clipboard.write([ |
| + | new ClipboardItem({ 'image/png': blob }) |
| + | ]) |
| + | alert('QR code copied to clipboard!') |
| + | } catch (error) { |
| + | alert('Failed to copy QR code') |
| + | } |
| + | } |
| + | </script> |
| \ No newline at end of file | |
frontend/src/main.js
+12
-0
| @@ | @@ -0,0 +1,12 @@ |
| + | import { createApp } from 'vue' |
| + | import { createPinia } from 'pinia' |
| + | import router from './router' |
| + | import App from './App.vue' |
| + | import './style.css' |
| + | |
| + | const app = createApp(App) |
| + | |
| + | app.use(createPinia()) |
| + | app.use(router) |
| + | |
| + | app.mount('#app') |
| \ No newline at end of file | |
frontend/src/router/index.js
+53
-0
| @@ | @@ -0,0 +1,53 @@ |
| + | import { createRouter, createWebHistory } from 'vue-router' |
| + | import { useAuthStore } from '../stores/auth' |
| + | |
| + | const router = createRouter({ |
| + | history: createWebHistory(), |
| + | routes: [ |
| + | { |
| + | path: '/', |
| + | name: 'home', |
| + | component: () => import('../views/Home.vue'), |
| + | meta: { requiresAuth: false } |
| + | }, |
| + | { |
| + | path: '/login', |
| + | name: 'login', |
| + | component: () => import('../views/Login.vue'), |
| + | meta: { requiresAuth: false } |
| + | }, |
| + | { |
| + | path: '/auth/verify', |
| + | name: 'verify', |
| + | component: () => import('../views/Verify.vue'), |
| + | meta: { requiresAuth: false } |
| + | }, |
| + | { |
| + | path: '/dashboard', |
| + | name: 'dashboard', |
| + | component: () => import('../views/Dashboard.vue'), |
| + | meta: { requiresAuth: true } |
| + | }, |
| + | { |
| + | path: '/analytics/:id', |
| + | name: 'analytics', |
| + | component: () => import('../views/Analytics.vue'), |
| + | meta: { requiresAuth: true } |
| + | } |
| + | ] |
| + | }) |
| + | |
| + | // Navigation guard for authentication |
| + | router.beforeEach((to, from, next) => { |
| + | const authStore = useAuthStore() |
| + | |
| + | if (to.meta.requiresAuth && !authStore.isAuthenticated) { |
| + | next('/login') |
| + | } else if (to.path === '/login' && authStore.isAuthenticated) { |
| + | next('/dashboard') |
| + | } else { |
| + | next() |
| + | } |
| + | }) |
| + | |
| + | export default router |
| \ No newline at end of file | |
frontend/src/services/api.js
+24
-0
| @@ | @@ -0,0 +1,24 @@ |
| + | import axios from 'axios' |
| + | |
| + | const api = axios.create({ |
| + | baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8787/api', |
| + | headers: { |
| + | 'Content-Type': 'application/json' |
| + | } |
| + | }) |
| + | |
| + | // Request interceptor for error handling |
| + | api.interceptors.response.use( |
| + | response => response, |
| + | error => { |
| + | if (error.response?.status === 401) { |
| + | // Clear auth and redirect to login |
| + | localStorage.removeItem('token') |
| + | localStorage.removeItem('user') |
| + | window.location.href = '/login' |
| + | } |
| + | return Promise.reject(error) |
| + | } |
| + | ) |
| + | |
| + | export default api |
| \ No newline at end of file | |
frontend/src/services/qrcode.js
+81
-0
| @@ | @@ -0,0 +1,81 @@ |
| + | import QRCode from 'qrcode' |
| + | |
| + | export async function generateQRCode(text, options = {}) { |
| + | const defaultOptions = { |
| + | errorCorrectionLevel: 'H', // High - allows 30% damage for logo overlay |
| + | type: 'image/png', |
| + | quality: 0.92, |
| + | margin: 2, |
| + | color: { |
| + | dark: '#000000', |
| + | light: '#FFFFFF' |
| + | }, |
| + | width: 512 |
| + | } |
| + | |
| + | const qrOptions = { ...defaultOptions, ...options } |
| + | |
| + | try { |
| + | const dataUrl = await QRCode.toDataURL(text, qrOptions) |
| + | return dataUrl |
| + | } catch (error) { |
| + | console.error('QR Code generation error:', error) |
| + | throw error |
| + | } |
| + | } |
| + | |
| + | export async function generateQRCodeWithLogo(text, logoUrl, options = {}) { |
| + | const canvas = document.createElement('canvas') |
| + | const ctx = canvas.getContext('2d') |
| + | |
| + | canvas.width = options.width || 512 |
| + | canvas.height = options.height || 512 |
| + | |
| + | // Generate QR code |
| + | const qrDataUrl = await generateQRCode(text, { |
| + | ...options, |
| + | errorCorrectionLevel: 'H' |
| + | }) |
| + | |
| + | // Draw QR code |
| + | const qrImage = new Image() |
| + | qrImage.src = qrDataUrl |
| + | |
| + | return new Promise((resolve, reject) => { |
| + | qrImage.onload = () => { |
| + | ctx.drawImage(qrImage, 0, 0, canvas.width, canvas.height) |
| + | |
| + | if (logoUrl) { |
| + | // Add logo |
| + | const logo = new Image() |
| + | logo.crossOrigin = 'anonymous' |
| + | logo.src = logoUrl |
| + | |
| + | logo.onload = () => { |
| + | // Calculate logo size (max 30% of QR code) |
| + | const logoSize = Math.floor(canvas.width * 0.3) |
| + | const logoX = (canvas.width - logoSize) / 2 |
| + | const logoY = (canvas.height - logoSize) / 2 |
| + | |
| + | // Draw white background for logo |
| + | ctx.fillStyle = 'white' |
| + | ctx.fillRect(logoX - 10, logoY - 10, logoSize + 20, logoSize + 20) |
| + | |
| + | // Draw logo |
| + | ctx.drawImage(logo, logoX, logoY, logoSize, logoSize) |
| + | |
| + | resolve(canvas.toDataURL()) |
| + | } |
| + | |
| + | logo.onerror = () => { |
| + | // If logo fails to load, return QR without logo |
| + | resolve(qrDataUrl) |
| + | } |
| + | } else { |
| + | resolve(qrDataUrl) |
| + | } |
| + | } |
| + | |
| + | qrImage.onerror = reject |
| + | }) |
| + | } |
| \ No newline at end of file | |
frontend/src/stores/auth.js
+55
-0
| @@ | @@ -0,0 +1,55 @@ |
| + | import { defineStore } from 'pinia' |
| + | import { ref, computed } from 'vue' |
| + | import api from '../services/api' |
| + | |
| + | export const useAuthStore = defineStore('auth', () => { |
| + | const token = ref(localStorage.getItem('token')) |
| + | const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) |
| + | |
| + | const isAuthenticated = computed(() => !!token.value) |
| + | |
| + | async function requestMagicLink(email) { |
| + | const response = await api.post('/auth/request', { email }) |
| + | return response.data |
| + | } |
| + | |
| + | async function verifyMagicLink(verifyToken) { |
| + | const response = await api.post('/auth/verify', { token: verifyToken }) |
| + | const { token: authToken, user: userData } = response.data |
| + | |
| + | token.value = authToken |
| + | user.value = userData |
| + | |
| + | localStorage.setItem('token', authToken) |
| + | localStorage.setItem('user', JSON.stringify(userData)) |
| + | |
| + | // Set default authorization header |
| + | api.defaults.headers.common['Authorization'] = `Bearer ${authToken}` |
| + | |
| + | return response.data |
| + | } |
| + | |
| + | function logout() { |
| + | token.value = null |
| + | user.value = null |
| + | |
| + | localStorage.removeItem('token') |
| + | localStorage.removeItem('user') |
| + | |
| + | delete api.defaults.headers.common['Authorization'] |
| + | } |
| + | |
| + | // Initialize auth header if token exists |
| + | if (token.value) { |
| + | api.defaults.headers.common['Authorization'] = `Bearer ${token.value}` |
| + | } |
| + | |
| + | return { |
| + | token, |
| + | user, |
| + | isAuthenticated, |
| + | requestMagicLink, |
| + | verifyMagicLink, |
| + | logout |
| + | } |
| + | }) |
| \ No newline at end of file | |
frontend/src/stores/entries.js
+94
-0
| @@ | @@ -0,0 +1,94 @@ |
| + | import { defineStore } from 'pinia' |
| + | import { ref } from 'vue' |
| + | import api from '../services/api' |
| + | |
| + | export const useEntriesStore = defineStore('entries', () => { |
| + | const entries = ref([]) |
| + | const loading = ref(false) |
| + | const error = ref(null) |
| + | |
| + | async function fetchEntries() { |
| + | loading.value = true |
| + | error.value = null |
| + | try { |
| + | const response = await api.get('/entries') |
| + | entries.value = response.data.entries |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to fetch entries' |
| + | throw err |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | |
| + | async function createEntry(data) { |
| + | loading.value = true |
| + | error.value = null |
| + | try { |
| + | const response = await api.post('/entries', data) |
| + | entries.value.unshift(response.data.entry) |
| + | return response.data.entry |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to create entry' |
| + | throw err |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | |
| + | async function updateEntry(id, data) { |
| + | loading.value = true |
| + | error.value = null |
| + | try { |
| + | await api.put(`/entries/${id}`, data) |
| + | const index = entries.value.findIndex(e => e.id === id) |
| + | if (index !== -1) { |
| + | entries.value[index] = { ...entries.value[index], ...data } |
| + | } |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to update entry' |
| + | throw err |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | |
| + | async function deleteEntry(id) { |
| + | loading.value = true |
| + | error.value = null |
| + | try { |
| + | await api.delete(`/entries/${id}`) |
| + | entries.value = entries.value.filter(e => e.id !== id) |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to delete entry' |
| + | throw err |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | |
| + | async function fetchAnalytics(id) { |
| + | loading.value = true |
| + | error.value = null |
| + | try { |
| + | const response = await api.get(`/analytics/${id}`) |
| + | return response.data |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to fetch analytics' |
| + | throw err |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | |
| + | return { |
| + | entries, |
| + | loading, |
| + | error, |
| + | fetchEntries, |
| + | createEntry, |
| + | updateEntry, |
| + | deleteEntry, |
| + | fetchAnalytics |
| + | } |
| + | }) |
| \ No newline at end of file | |
frontend/src/style.css
+3
-0
| @@ | @@ -0,0 +1,3 @@ |
| + | @tailwind base; |
| + | @tailwind components; |
| + | @tailwind utilities; |
| \ No newline at end of file | |
frontend/src/views/Analytics.vue
+229
-0
| @@ | @@ -0,0 +1,229 @@ |
| + | <template> |
| + | <div class="min-h-screen bg-gray-50"> |
| + | <!-- Header --> |
| + | <header class="bg-white shadow-sm border-b"> |
| + | <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| + | <div class="flex justify-between items-center h-16"> |
| + | <div class="flex items-center space-x-4"> |
| + | <router-link to="/dashboard" class="flex items-center space-x-2"> |
| + | <LinkIcon class="h-6 w-6 text-indigo-600" /> |
| + | <span class="text-xl font-bold text-gray-900">QRurl</span> |
| + | </router-link> |
| + | <span class="text-gray-400">/</span> |
| + | <span class="text-gray-600">Analytics</span> |
| + | </div> |
| + | |
| + | <router-link |
| + | to="/dashboard" |
| + | class="text-gray-500 hover:text-gray-700 transition flex items-center space-x-2" |
| + | > |
| + | <ArrowLeftIcon class="h-4 w-4" /> |
| + | <span>Back to Dashboard</span> |
| + | </router-link> |
| + | </div> |
| + | </div> |
| + | </header> |
| + | |
| + | <!-- Main Content --> |
| + | <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> |
| + | <div v-if="loading" class="bg-white rounded-lg shadow-sm p-8"> |
| + | <div class="text-center"> |
| + | <Loader2Icon class="h-8 w-8 text-gray-400 animate-spin mx-auto mb-2" /> |
| + | <p class="text-gray-500">Loading analytics...</p> |
| + | </div> |
| + | </div> |
| + | |
| + | <div v-else-if="error" class="bg-white rounded-lg shadow-sm p-8"> |
| + | <div class="text-center"> |
| + | <AlertCircleIcon class="h-12 w-12 text-red-400 mx-auto mb-3" /> |
| + | <p class="text-gray-900 font-semibold">Failed to load analytics</p> |
| + | <p class="text-gray-500 mt-1">{{ error }}</p> |
| + | </div> |
| + | </div> |
| + | |
| + | <div v-else> |
| + | <!-- Link Info --> |
| + | <div class="bg-white rounded-lg shadow-sm p-6 mb-6"> |
| + | <h1 class="text-2xl font-bold text-gray-900 mb-2">{{ analyticsData.entry.name }}</h1> |
| + | <div class="space-y-2"> |
| + | <p class="text-sm text-gray-600"> |
| + | <span class="font-medium">Short URL:</span> |
| + | <a :href="getShortUrl(analyticsData.entry.slug)" target="_blank" class="text-indigo-600 hover:text-indigo-700 ml-2"> |
| + | {{ getShortUrl(analyticsData.entry.slug) }} |
| + | </a> |
| + | </p> |
| + | </div> |
| + | </div> |
| + | |
| + | <!-- Stats Cards --> |
| + | <div class="grid md:grid-cols-3 gap-6 mb-6"> |
| + | <div class="bg-white rounded-lg shadow-sm p-6"> |
| + | <div class="flex items-center justify-between"> |
| + | <div> |
| + | <p class="text-sm font-medium text-gray-600">Total Clicks</p> |
| + | <p class="text-3xl font-bold text-gray-900 mt-1">{{ analyticsData.entry.clickCount || 0 }}</p> |
| + | </div> |
| + | <div class="p-3 bg-indigo-100 rounded-lg"> |
| + | <MousePointerClickIcon class="h-6 w-6 text-indigo-600" /> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <div class="bg-white rounded-lg shadow-sm p-6"> |
| + | <div class="flex items-center justify-between"> |
| + | <div> |
| + | <p class="text-sm font-medium text-gray-600">Unique Visitors</p> |
| + | <p class="text-3xl font-bold text-gray-900 mt-1">{{ uniqueVisitors }}</p> |
| + | </div> |
| + | <div class="p-3 bg-green-100 rounded-lg"> |
| + | <UsersIcon class="h-6 w-6 text-green-600" /> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <div class="bg-white rounded-lg shadow-sm p-6"> |
| + | <div class="flex items-center justify-between"> |
| + | <div> |
| + | <p class="text-sm font-medium text-gray-600">Countries</p> |
| + | <p class="text-3xl font-bold text-gray-900 mt-1">{{ uniqueCountries }}</p> |
| + | </div> |
| + | <div class="p-3 bg-purple-100 rounded-lg"> |
| + | <GlobeIcon class="h-6 w-6 text-purple-600" /> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <!-- Clicks by Date --> |
| + | <div class="bg-white rounded-lg shadow-sm p-6 mb-6"> |
| + | <h2 class="text-lg font-semibold mb-4">Clicks Over Time</h2> |
| + | <div v-if="analyticsData.analytics.length === 0" class="text-center py-8 text-gray-500"> |
| + | No click data available yet |
| + | </div> |
| + | <div v-else class="space-y-2"> |
| + | <div v-for="day in clicksByDate" :key="day.date" class="flex items-center"> |
| + | <span class="text-sm text-gray-600 w-24">{{ formatDate(day.date) }}</span> |
| + | <div class="flex-1 mx-4"> |
| + | <div class="h-6 bg-gray-100 rounded-full overflow-hidden"> |
| + | <div |
| + | class="h-full bg-indigo-600 rounded-full" |
| + | :style="`width: ${(day.clicks / maxClicks) * 100}%`" |
| + | ></div> |
| + | </div> |
| + | </div> |
| + | <span class="text-sm font-medium text-gray-900 w-12 text-right">{{ day.clicks }}</span> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <!-- Countries --> |
| + | <div class="bg-white rounded-lg shadow-sm p-6"> |
| + | <h2 class="text-lg font-semibold mb-4">Top Countries</h2> |
| + | <div v-if="countriesData.length === 0" class="text-center py-8 text-gray-500"> |
| + | No geographic data available yet |
| + | </div> |
| + | <div v-else class="space-y-3"> |
| + | <div v-for="country in countriesData" :key="country.name" class="flex items-center justify-between"> |
| + | <span class="text-sm text-gray-700">{{ country.name || 'Unknown' }}</span> |
| + | <span class="text-sm font-medium text-gray-900">{{ country.clicks }} clicks</span> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </main> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { ref, computed, onMounted } from 'vue' |
| + | import { useRoute } from 'vue-router' |
| + | import { |
| + | Link as LinkIcon, |
| + | ArrowLeft as ArrowLeftIcon, |
| + | Loader2 as Loader2Icon, |
| + | AlertCircle as AlertCircleIcon, |
| + | MousePointerClick as MousePointerClickIcon, |
| + | Users as UsersIcon, |
| + | Globe as GlobeIcon |
| + | } from 'lucide-vue-next' |
| + | import { useEntriesStore } from '../stores/entries' |
| + | |
| + | const route = useRoute() |
| + | const entriesStore = useEntriesStore() |
| + | |
| + | const loading = ref(true) |
| + | const error = ref('') |
| + | const analyticsData = ref(null) |
| + | |
| + | const uniqueVisitors = computed(() => { |
| + | if (!analyticsData.value?.analytics) return 0 |
| + | const unique = new Set(analyticsData.value.analytics.map(a => a.ip_hash)) |
| + | return unique.size |
| + | }) |
| + | |
| + | const uniqueCountries = computed(() => { |
| + | if (!analyticsData.value?.analytics) return 0 |
| + | const countries = new Set(analyticsData.value.analytics.map(a => a.country).filter(Boolean)) |
| + | return countries.size |
| + | }) |
| + | |
| + | const clicksByDate = computed(() => { |
| + | if (!analyticsData.value?.analytics) return [] |
| + | |
| + | const grouped = {} |
| + | analyticsData.value.analytics.forEach(item => { |
| + | const date = item.date |
| + | if (!grouped[date]) { |
| + | grouped[date] = 0 |
| + | } |
| + | grouped[date] += item.clicks || 1 |
| + | }) |
| + | |
| + | return Object.entries(grouped) |
| + | .map(([date, clicks]) => ({ date, clicks })) |
| + | .sort((a, b) => new Date(b.date) - new Date(a.date)) |
| + | .slice(0, 7) |
| + | }) |
| + | |
| + | const maxClicks = computed(() => { |
| + | return Math.max(...clicksByDate.value.map(d => d.clicks), 1) |
| + | }) |
| + | |
| + | const countriesData = computed(() => { |
| + | if (!analyticsData.value?.analytics) return [] |
| + | |
| + | const grouped = {} |
| + | analyticsData.value.analytics.forEach(item => { |
| + | const country = item.country || 'Unknown' |
| + | if (!grouped[country]) { |
| + | grouped[country] = 0 |
| + | } |
| + | grouped[country] += item.clicks || 1 |
| + | }) |
| + | |
| + | return Object.entries(grouped) |
| + | .map(([name, clicks]) => ({ name, clicks })) |
| + | .sort((a, b) => b.clicks - a.clicks) |
| + | .slice(0, 10) |
| + | }) |
| + | |
| + | onMounted(async () => { |
| + | try { |
| + | const id = route.params.id |
| + | analyticsData.value = await entriesStore.fetchAnalytics(id) |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to load analytics' |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | }) |
| + | |
| + | function getShortUrl(slug) { |
| + | return `${import.meta.env.VITE_SHORT_URL || 'http://localhost:8787'}/${slug}` |
| + | } |
| + | |
| + | function formatDate(dateString) { |
| + | const date = new Date(dateString) |
| + | return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) |
| + | } |
| + | </script> |
| \ No newline at end of file | |
frontend/src/views/Dashboard.vue
+259
-0
| @@ | @@ -0,0 +1,259 @@ |
| + | <template> |
| + | <div class="min-h-screen bg-gray-50"> |
| + | <!-- Header --> |
| + | <header class="bg-white shadow-sm border-b"> |
| + | <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| + | <div class="flex justify-between items-center h-16"> |
| + | <div class="flex items-center space-x-2"> |
| + | <LinkIcon class="h-6 w-6 text-indigo-600" /> |
| + | <span class="text-xl font-bold text-gray-900">QRurl</span> |
| + | </div> |
| + | |
| + | <div class="flex items-center space-x-4"> |
| + | <span class="text-sm text-gray-600">{{ authStore.user?.email }}</span> |
| + | <button |
| + | @click="handleLogout" |
| + | class="text-gray-500 hover:text-gray-700 transition" |
| + | > |
| + | <LogOutIcon class="h-5 w-5" /> |
| + | </button> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </header> |
| + | |
| + | <!-- Main Content --> |
| + | <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> |
| + | <!-- Create New Link --> |
| + | <div class="bg-white rounded-lg shadow-sm p-6 mb-8"> |
| + | <h2 class="text-lg font-semibold mb-4">Create New Short Link</h2> |
| + | |
| + | <form @submit.prevent="handleCreate" class="space-y-4"> |
| + | <div class="grid md:grid-cols-2 gap-4"> |
| + | <div> |
| + | <label class="block text-sm font-medium text-gray-700 mb-1"> |
| + | Name |
| + | </label> |
| + | <input |
| + | v-model="newEntry.name" |
| + | type="text" |
| + | required |
| + | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" |
| + | placeholder="My awesome link" |
| + | /> |
| + | </div> |
| + | |
| + | <div> |
| + | <label class="block text-sm font-medium text-gray-700 mb-1"> |
| + | Custom Slug (optional) |
| + | </label> |
| + | <input |
| + | v-model="newEntry.customSlug" |
| + | type="text" |
| + | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" |
| + | placeholder="my-custom-slug" |
| + | pattern="[a-zA-Z0-9-]{3,50}" |
| + | /> |
| + | </div> |
| + | </div> |
| + | |
| + | <div> |
| + | <label class="block text-sm font-medium text-gray-700 mb-1"> |
| + | Destination URL |
| + | </label> |
| + | <input |
| + | v-model="newEntry.url" |
| + | type="url" |
| + | required |
| + | class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" |
| + | placeholder="https://example.com" |
| + | /> |
| + | </div> |
| + | |
| + | <div class="flex justify-end"> |
| + | <button |
| + | type="submit" |
| + | :disabled="creating" |
| + | class="bg-indigo-600 text-white px-6 py-2 rounded-lg font-semibold hover:bg-indigo-700 transition disabled:opacity-50" |
| + | > |
| + | {{ creating ? 'Creating...' : 'Create Link' }} |
| + | </button> |
| + | </div> |
| + | </form> |
| + | </div> |
| + | |
| + | <!-- Links List --> |
| + | <div class="bg-white rounded-lg shadow-sm"> |
| + | <div class="px-6 py-4 border-b"> |
| + | <h2 class="text-lg font-semibold">Your Links</h2> |
| + | </div> |
| + | |
| + | <div v-if="loading" class="p-8 text-center"> |
| + | <Loader2Icon class="h-8 w-8 text-gray-400 animate-spin mx-auto mb-2" /> |
| + | <p class="text-gray-500">Loading your links...</p> |
| + | </div> |
| + | |
| + | <div v-else-if="entriesStore.entries.length === 0" class="p-8 text-center"> |
| + | <LinkIcon class="h-12 w-12 text-gray-300 mx-auto mb-3" /> |
| + | <p class="text-gray-500">No links yet. Create your first one above!</p> |
| + | </div> |
| + | |
| + | <div v-else class="divide-y"> |
| + | <div |
| + | v-for="entry in entriesStore.entries" |
| + | :key="entry.id" |
| + | class="p-6 hover:bg-gray-50 transition" |
| + | > |
| + | <div class="flex items-start justify-between"> |
| + | <div class="flex-1"> |
| + | <h3 class="font-semibold text-gray-900">{{ entry.name }}</h3> |
| + | <div class="mt-1 space-y-1"> |
| + | <p class="text-sm text-indigo-600"> |
| + | {{ getShortUrl(entry.slug) }} |
| + | <button |
| + | @click="copyToClipboard(getShortUrl(entry.slug))" |
| + | class="ml-2 text-gray-400 hover:text-gray-600" |
| + | > |
| + | <CopyIcon class="h-4 w-4 inline" /> |
| + | </button> |
| + | </p> |
| + | <p class="text-sm text-gray-500 truncate"> |
| + | โ {{ entry.original_url }} |
| + | </p> |
| + | </div> |
| + | <div class="mt-2 flex items-center space-x-4 text-sm text-gray-500"> |
| + | <span>{{ entry.click_count || 0 }} clicks</span> |
| + | <span>Created {{ formatDate(entry.created_at) }}</span> |
| + | </div> |
| + | </div> |
| + | |
| + | <div class="flex items-center space-x-2 ml-4"> |
| + | <button |
| + | @click="showQRCode(entry)" |
| + | class="p-2 text-gray-400 hover:text-gray-600 transition" |
| + | title="Show QR Code" |
| + | > |
| + | <QrCodeIcon class="h-5 w-5" /> |
| + | </button> |
| + | <router-link |
| + | :to="`/analytics/${entry.id}`" |
| + | class="p-2 text-gray-400 hover:text-gray-600 transition" |
| + | title="View Analytics" |
| + | > |
| + | <ChartBarIcon class="h-5 w-5" /> |
| + | </router-link> |
| + | <button |
| + | @click="deleteEntry(entry.id)" |
| + | class="p-2 text-gray-400 hover:text-red-600 transition" |
| + | title="Delete" |
| + | > |
| + | <TrashIcon class="h-5 w-5" /> |
| + | </button> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </main> |
| + | |
| + | <!-- QR Code Modal --> |
| + | <QRCodeModal |
| + | v-if="selectedEntry" |
| + | :entry="selectedEntry" |
| + | @close="selectedEntry = null" |
| + | /> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { ref, onMounted } from 'vue' |
| + | import { useRouter } from 'vue-router' |
| + | import { |
| + | Link as LinkIcon, |
| + | LogOut as LogOutIcon, |
| + | Loader2 as Loader2Icon, |
| + | Copy as CopyIcon, |
| + | QrCode as QrCodeIcon, |
| + | BarChart3 as ChartBarIcon, |
| + | Trash2 as TrashIcon |
| + | } from 'lucide-vue-next' |
| + | import { useAuthStore } from '../stores/auth' |
| + | import { useEntriesStore } from '../stores/entries' |
| + | import QRCodeModal from '../components/QRCodeModal.vue' |
| + | |
| + | const router = useRouter() |
| + | const authStore = useAuthStore() |
| + | const entriesStore = useEntriesStore() |
| + | |
| + | const loading = ref(true) |
| + | const creating = ref(false) |
| + | const selectedEntry = ref(null) |
| + | |
| + | const newEntry = ref({ |
| + | name: '', |
| + | url: '', |
| + | customSlug: '' |
| + | }) |
| + | |
| + | onMounted(async () => { |
| + | try { |
| + | await entriesStore.fetchEntries() |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | }) |
| + | |
| + | async function handleCreate() { |
| + | creating.value = true |
| + | try { |
| + | await entriesStore.createEntry(newEntry.value) |
| + | newEntry.value = { name: '', url: '', customSlug: '' } |
| + | } catch (error) { |
| + | alert(error.response?.data?.error || 'Failed to create link') |
| + | } finally { |
| + | creating.value = false |
| + | } |
| + | } |
| + | |
| + | async function deleteEntry(id) { |
| + | if (confirm('Are you sure you want to delete this link?')) { |
| + | try { |
| + | await entriesStore.deleteEntry(id) |
| + | } catch (error) { |
| + | alert('Failed to delete link') |
| + | } |
| + | } |
| + | } |
| + | |
| + | function handleLogout() { |
| + | authStore.logout() |
| + | router.push('/') |
| + | } |
| + | |
| + | function getShortUrl(slug) { |
| + | return `${import.meta.env.VITE_SHORT_URL || 'http://localhost:8787'}/${slug}` |
| + | } |
| + | |
| + | function copyToClipboard(text) { |
| + | navigator.clipboard.writeText(text) |
| + | .then(() => alert('Copied to clipboard!')) |
| + | .catch(() => alert('Failed to copy')) |
| + | } |
| + | |
| + | function showQRCode(entry) { |
| + | selectedEntry.value = entry |
| + | } |
| + | |
| + | function formatDate(dateString) { |
| + | const date = new Date(dateString) |
| + | const now = new Date() |
| + | const diff = now - date |
| + | const days = Math.floor(diff / (1000 * 60 * 60 * 24)) |
| + | |
| + | if (days === 0) return 'today' |
| + | if (days === 1) return 'yesterday' |
| + | if (days < 30) return `${days} days ago` |
| + | |
| + | return date.toLocaleDateString() |
| + | } |
| + | </script> |
| \ No newline at end of file | |
frontend/src/views/Home.vue
+98
-0
| @@ | @@ -0,0 +1,98 @@ |
| + | <template> |
| + | <div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> |
| + | <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| + | <nav class="flex justify-between items-center py-6"> |
| + | <div class="flex items-center space-x-2"> |
| + | <LinkIcon class="h-8 w-8 text-indigo-600" /> |
| + | <span class="text-2xl font-bold text-gray-900">QRurl</span> |
| + | </div> |
| + | <div class="flex items-center space-x-4"> |
| + | <router-link |
| + | v-if="!authStore.isAuthenticated" |
| + | to="/login" |
| + | class="text-gray-700 hover:text-gray-900" |
| + | > |
| + | Sign In |
| + | </router-link> |
| + | <router-link |
| + | v-if="authStore.isAuthenticated" |
| + | to="/dashboard" |
| + | class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition" |
| + | > |
| + | Dashboard |
| + | </router-link> |
| + | </div> |
| + | </nav> |
| + | |
| + | <main class="py-20"> |
| + | <div class="text-center"> |
| + | <h1 class="text-5xl font-bold text-gray-900 mb-6"> |
| + | Short Links, Beautiful QR Codes |
| + | </h1> |
| + | <p class="text-xl text-gray-600 mb-12 max-w-2xl mx-auto"> |
| + | Create custom short URLs with branded QR codes. Track analytics, manage your links, |
| + | and share them with style. |
| + | </p> |
| + | |
| + | <div v-if="!authStore.isAuthenticated" class="space-y-4"> |
| + | <router-link |
| + | to="/login" |
| + | class="inline-block bg-indigo-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:bg-indigo-700 transition" |
| + | > |
| + | Get Started Free |
| + | </router-link> |
| + | <p class="text-sm text-gray-500">No credit card required</p> |
| + | </div> |
| + | |
| + | <div v-else> |
| + | <router-link |
| + | to="/dashboard" |
| + | class="inline-block bg-indigo-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:bg-indigo-700 transition" |
| + | > |
| + | Go to Dashboard |
| + | </router-link> |
| + | </div> |
| + | </div> |
| + | |
| + | <div class="mt-20 grid md:grid-cols-3 gap-8"> |
| + | <div class="bg-white p-8 rounded-xl shadow-sm"> |
| + | <div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4"> |
| + | <LinkIcon class="h-6 w-6 text-indigo-600" /> |
| + | </div> |
| + | <h3 class="text-xl font-semibold mb-2">Custom Short URLs</h3> |
| + | <p class="text-gray-600"> |
| + | Create memorable short links with custom slugs or auto-generated codes |
| + | </p> |
| + | </div> |
| + | |
| + | <div class="bg-white p-8 rounded-xl shadow-sm"> |
| + | <div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4"> |
| + | <QrCodeIcon class="h-6 w-6 text-indigo-600" /> |
| + | </div> |
| + | <h3 class="text-xl font-semibold mb-2">Branded QR Codes</h3> |
| + | <p class="text-gray-600"> |
| + | Generate QR codes with your logo embedded for professional sharing |
| + | </p> |
| + | </div> |
| + | |
| + | <div class="bg-white p-8 rounded-xl shadow-sm"> |
| + | <div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4"> |
| + | <ChartBarIcon class="h-6 w-6 text-indigo-600" /> |
| + | </div> |
| + | <h3 class="text-xl font-semibold mb-2">Analytics & Insights</h3> |
| + | <p class="text-gray-600"> |
| + | Track clicks, geographic data, and referrers for your links |
| + | </p> |
| + | </div> |
| + | </div> |
| + | </main> |
| + | </div> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { Link as LinkIcon, QrCode as QrCodeIcon, BarChart3 as ChartBarIcon } from 'lucide-vue-next' |
| + | import { useAuthStore } from '../stores/auth' |
| + | |
| + | const authStore = useAuthStore() |
| + | </script> |
| \ No newline at end of file | |
frontend/src/views/Login.vue
+104
-0
| @@ | @@ -0,0 +1,104 @@ |
| + | <template> |
| + | <div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center px-4"> |
| + | <div class="max-w-md w-full"> |
| + | <div class="bg-white rounded-2xl shadow-xl p-8"> |
| + | <div class="text-center mb-8"> |
| + | <router-link to="/" class="inline-flex items-center space-x-2 mb-6"> |
| + | <LinkIcon class="h-8 w-8 text-indigo-600" /> |
| + | <span class="text-2xl font-bold text-gray-900">QRurl</span> |
| + | </router-link> |
| + | <h2 class="text-3xl font-bold text-gray-900">Welcome back</h2> |
| + | <p class="text-gray-600 mt-2">Sign in with your email</p> |
| + | </div> |
| + | |
| + | <form @submit.prevent="handleSubmit" class="space-y-6"> |
| + | <div> |
| + | <label for="email" class="block text-sm font-medium text-gray-700 mb-2"> |
| + | Email address |
| + | </label> |
| + | <input |
| + | id="email" |
| + | v-model="email" |
| + | type="email" |
| + | required |
| + | class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" |
| + | placeholder="you@example.com" |
| + | :disabled="loading" |
| + | /> |
| + | </div> |
| + | |
| + | <button |
| + | type="submit" |
| + | :disabled="loading" |
| + | class="w-full bg-indigo-600 text-white py-3 px-4 rounded-lg font-semibold hover:bg-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center" |
| + | > |
| + | <span v-if="!loading">Send Magic Link</span> |
| + | <span v-else class="flex items-center"> |
| + | <Loader2Icon class="animate-spin -ml-1 mr-3 h-5 w-5" /> |
| + | Sending... |
| + | </span> |
| + | </button> |
| + | </form> |
| + | |
| + | <div v-if="success" class="mt-6 p-4 bg-green-50 border border-green-200 rounded-lg"> |
| + | <div class="flex items-start"> |
| + | <CheckCircleIcon class="h-5 w-5 text-green-500 mt-0.5 mr-2 flex-shrink-0" /> |
| + | <div> |
| + | <p class="text-green-800 font-medium">Magic link sent!</p> |
| + | <p class="text-green-700 text-sm mt-1"> |
| + | Check your email for a login link. It will expire in 15 minutes. |
| + | </p> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <div v-if="error" class="mt-6 p-4 bg-red-50 border border-red-200 rounded-lg"> |
| + | <div class="flex items-start"> |
| + | <AlertCircleIcon class="h-5 w-5 text-red-500 mt-0.5 mr-2 flex-shrink-0" /> |
| + | <div> |
| + | <p class="text-red-800 font-medium">Error</p> |
| + | <p class="text-red-700 text-sm mt-1">{{ error }}</p> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | |
| + | <div class="mt-8 text-center text-sm text-gray-600"> |
| + | <p> |
| + | By signing in, you agree to our |
| + | <a href="#" class="text-indigo-600 hover:text-indigo-700"> Terms of Service</a> |
| + | and |
| + | <a href="#" class="text-indigo-600 hover:text-indigo-700"> Privacy Policy</a> |
| + | </p> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { ref } from 'vue' |
| + | import { Link as LinkIcon, Loader2 as Loader2Icon, CheckCircle as CheckCircleIcon, AlertCircle as AlertCircleIcon } from 'lucide-vue-next' |
| + | import { useAuthStore } from '../stores/auth' |
| + | |
| + | const authStore = useAuthStore() |
| + | |
| + | const email = ref('') |
| + | const loading = ref(false) |
| + | const success = ref(false) |
| + | const error = ref('') |
| + | |
| + | async function handleSubmit() { |
| + | loading.value = true |
| + | error.value = '' |
| + | success.value = false |
| + | |
| + | try { |
| + | await authStore.requestMagicLink(email.value) |
| + | success.value = true |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Failed to send magic link. Please try again.' |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | } |
| + | </script> |
| \ No newline at end of file | |
frontend/src/views/Verify.vue
+76
-0
| @@ | @@ -0,0 +1,76 @@ |
| + | <template> |
| + | <div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center px-4"> |
| + | <div class="max-w-md w-full"> |
| + | <div class="bg-white rounded-2xl shadow-xl p-8"> |
| + | <div class="text-center"> |
| + | <router-link to="/" class="inline-flex items-center space-x-2 mb-6"> |
| + | <LinkIcon class="h-8 w-8 text-indigo-600" /> |
| + | <span class="text-2xl font-bold text-gray-900">QRurl</span> |
| + | </router-link> |
| + | |
| + | <div v-if="loading" class="py-8"> |
| + | <Loader2Icon class="h-12 w-12 text-indigo-600 animate-spin mx-auto mb-4" /> |
| + | <p class="text-gray-600">Verifying your login...</p> |
| + | </div> |
| + | |
| + | <div v-else-if="error" class="py-8"> |
| + | <AlertCircleIcon class="h-12 w-12 text-red-500 mx-auto mb-4" /> |
| + | <h3 class="text-xl font-semibold text-gray-900 mb-2">Verification Failed</h3> |
| + | <p class="text-gray-600 mb-6">{{ error }}</p> |
| + | <router-link |
| + | to="/login" |
| + | class="inline-block bg-indigo-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-indigo-700 transition" |
| + | > |
| + | Back to Login |
| + | </router-link> |
| + | </div> |
| + | |
| + | <div v-else-if="success" class="py-8"> |
| + | <CheckCircleIcon class="h-12 w-12 text-green-500 mx-auto mb-4" /> |
| + | <h3 class="text-xl font-semibold text-gray-900 mb-2">Success!</h3> |
| + | <p class="text-gray-600">Redirecting to dashboard...</p> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </div> |
| + | </template> |
| + | |
| + | <script setup> |
| + | import { ref, onMounted } from 'vue' |
| + | import { useRoute, useRouter } from 'vue-router' |
| + | import { Link as LinkIcon, Loader2 as Loader2Icon, CheckCircle as CheckCircleIcon, AlertCircle as AlertCircleIcon } from 'lucide-vue-next' |
| + | import { useAuthStore } from '../stores/auth' |
| + | |
| + | const route = useRoute() |
| + | const router = useRouter() |
| + | const authStore = useAuthStore() |
| + | |
| + | const loading = ref(true) |
| + | const success = ref(false) |
| + | const error = ref('') |
| + | |
| + | onMounted(async () => { |
| + | const token = route.query.token |
| + | |
| + | if (!token) { |
| + | error.value = 'No verification token provided' |
| + | loading.value = false |
| + | return |
| + | } |
| + | |
| + | try { |
| + | await authStore.verifyMagicLink(token) |
| + | success.value = true |
| + | |
| + | // Redirect to dashboard after 2 seconds |
| + | setTimeout(() => { |
| + | router.push('/dashboard') |
| + | }, 2000) |
| + | } catch (err) { |
| + | error.value = err.response?.data?.error || 'Invalid or expired token' |
| + | } finally { |
| + | loading.value = false |
| + | } |
| + | }) |
| + | </script> |
| \ No newline at end of file | |
frontend/tailwind.config.js
+13
-0
| @@ | @@ -0,0 +1,13 @@ |
| + | /** @type {import('tailwindcss').Config} */ |
| + | export default { |
| + | content: [ |
| + | "./index.html", |
| + | "./src/**/*.{vue,js,ts,jsx,tsx}", |
| + | ], |
| + | theme: { |
| + | extend: {}, |
| + | }, |
| + | plugins: [ |
| + | require('@tailwindcss/forms'), |
| + | ], |
| + | } |
| \ No newline at end of file | |
frontend/vite.config.js
+16
-0
| @@ | @@ -0,0 +1,16 @@ |
| + | import { defineConfig } from 'vite' |
| + | import vue from '@vitejs/plugin-vue' |
| + | |
| + | // https://vite.dev/config/ |
| + | export default defineConfig({ |
| + | plugins: [vue()], |
| + | server: { |
| + | port: 3000, |
| + | proxy: { |
| + | '/api': { |
| + | target: 'http://localhost:8787', |
| + | changeOrigin: true, |
| + | } |
| + | } |
| + | } |
| + | }) |
package-lock.json
+1548
-0
| @@ | @@ -0,0 +1,1548 @@ |
| + | { |
| + | "name": "qrurl", |
| + | "version": "1.0.0", |
| + | "lockfileVersion": 3, |
| + | "requires": true, |
| + | "packages": { |
| + | "": { |
| + | "name": "qrurl", |
| + | "version": "1.0.0", |
| + | "license": "ISC", |
| + | "devDependencies": { |
| + | "wrangler": "^4.32.0" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/kv-asset-handler": { |
| + | "version": "0.4.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", |
| + | "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", |
| + | "dev": true, |
| + | "license": "MIT OR Apache-2.0", |
| + | "dependencies": { |
| + | "mime": "^3.0.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18.0.0" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/unenv-preset": { |
| + | "version": "2.6.2", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", |
| + | "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", |
| + | "dev": true, |
| + | "license": "MIT OR Apache-2.0", |
| + | "peerDependencies": { |
| + | "unenv": "2.0.0-rc.19", |
| + | "workerd": "^1.20250802.0" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "workerd": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/workerd-darwin-64": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", |
| + | "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/workerd-darwin-arm64": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", |
| + | "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/workerd-linux-64": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", |
| + | "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/workerd-linux-arm64": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", |
| + | "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/@cloudflare/workerd-windows-64": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", |
| + | "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=16" |
| + | } |
| + | }, |
| + | "node_modules/@cspotcode/source-map-support": { |
| + | "version": "0.8.1", |
| + | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", |
| + | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@jridgewell/trace-mapping": "0.3.9" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12" |
| + | } |
| + | }, |
| + | "node_modules/@emnapi/runtime": { |
| + | "version": "1.4.5", |
| + | "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", |
| + | "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "dependencies": { |
| + | "tslib": "^2.4.0" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/aix-ppc64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", |
| + | "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", |
| + | "cpu": [ |
| + | "ppc64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "aix" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-arm": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", |
| + | "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/android-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", |
| + | "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "android" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/darwin-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/darwin-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", |
| + | "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/freebsd-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/freebsd-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", |
| + | "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "freebsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-arm": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", |
| + | "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-ia32": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", |
| + | "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-loong64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", |
| + | "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", |
| + | "cpu": [ |
| + | "loong64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-mips64el": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", |
| + | "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", |
| + | "cpu": [ |
| + | "mips64el" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-ppc64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", |
| + | "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", |
| + | "cpu": [ |
| + | "ppc64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-riscv64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", |
| + | "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", |
| + | "cpu": [ |
| + | "riscv64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-s390x": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", |
| + | "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", |
| + | "cpu": [ |
| + | "s390x" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/linux-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", |
| + | "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/netbsd-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "netbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/netbsd-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", |
| + | "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "netbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/openbsd-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "openbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/openbsd-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", |
| + | "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "openbsd" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/sunos-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", |
| + | "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "sunos" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-arm64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", |
| + | "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-ia32": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", |
| + | "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@esbuild/win32-x64": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", |
| + | "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-darwin-arm64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", |
| + | "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-darwin-arm64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-darwin-x64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", |
| + | "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-darwin-x64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-darwin-arm64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", |
| + | "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-darwin-x64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", |
| + | "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linux-arm": { |
| + | "version": "1.0.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", |
| + | "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linux-arm64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", |
| + | "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linux-s390x": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", |
| + | "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", |
| + | "cpu": [ |
| + | "s390x" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linux-x64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", |
| + | "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linuxmusl-arm64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", |
| + | "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-libvips-linuxmusl-x64": { |
| + | "version": "1.0.4", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", |
| + | "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linux-arm": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", |
| + | "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", |
| + | "cpu": [ |
| + | "arm" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linux-arm": "1.0.5" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linux-arm64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", |
| + | "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linux-arm64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linux-s390x": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", |
| + | "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", |
| + | "cpu": [ |
| + | "s390x" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linux-s390x": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linux-x64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", |
| + | "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linux-x64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linuxmusl-arm64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", |
| + | "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", |
| + | "cpu": [ |
| + | "arm64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-linuxmusl-x64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", |
| + | "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "optional": true, |
| + | "os": [ |
| + | "linux" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-libvips-linuxmusl-x64": "1.0.4" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-wasm32": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", |
| + | "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", |
| + | "cpu": [ |
| + | "wasm32" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", |
| + | "optional": true, |
| + | "dependencies": { |
| + | "@emnapi/runtime": "^1.2.0" |
| + | }, |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-win32-ia32": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", |
| + | "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", |
| + | "cpu": [ |
| + | "ia32" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0 AND LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@img/sharp-win32-x64": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", |
| + | "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", |
| + | "cpu": [ |
| + | "x64" |
| + | ], |
| + | "dev": true, |
| + | "license": "Apache-2.0 AND LGPL-3.0-or-later", |
| + | "optional": true, |
| + | "os": [ |
| + | "win32" |
| + | ], |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | } |
| + | }, |
| + | "node_modules/@jridgewell/resolve-uri": { |
| + | "version": "3.1.2", |
| + | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", |
| + | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=6.0.0" |
| + | } |
| + | }, |
| + | "node_modules/@jridgewell/sourcemap-codec": { |
| + | "version": "1.5.5", |
| + | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", |
| + | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@jridgewell/trace-mapping": { |
| + | "version": "0.3.9", |
| + | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", |
| + | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@jridgewell/resolve-uri": "^3.0.3", |
| + | "@jridgewell/sourcemap-codec": "^1.4.10" |
| + | } |
| + | }, |
| + | "node_modules/@poppinss/colors": { |
| + | "version": "4.1.5", |
| + | "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", |
| + | "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "kleur": "^4.1.5" |
| + | } |
| + | }, |
| + | "node_modules/@poppinss/dumper": { |
| + | "version": "0.6.4", |
| + | "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", |
| + | "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@poppinss/colors": "^4.1.5", |
| + | "@sindresorhus/is": "^7.0.2", |
| + | "supports-color": "^10.0.0" |
| + | } |
| + | }, |
| + | "node_modules/@poppinss/exception": { |
| + | "version": "1.2.2", |
| + | "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", |
| + | "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/@sindresorhus/is": { |
| + | "version": "7.0.2", |
| + | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", |
| + | "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=18" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sindresorhus/is?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/@speed-highlight/core": { |
| + | "version": "1.2.7", |
| + | "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", |
| + | "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", |
| + | "dev": true, |
| + | "license": "CC0-1.0" |
| + | }, |
| + | "node_modules/acorn": { |
| + | "version": "8.14.0", |
| + | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", |
| + | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "bin": { |
| + | "acorn": "bin/acorn" |
| + | }, |
| + | "engines": { |
| + | "node": ">=0.4.0" |
| + | } |
| + | }, |
| + | "node_modules/acorn-walk": { |
| + | "version": "8.3.2", |
| + | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", |
| + | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=0.4.0" |
| + | } |
| + | }, |
| + | "node_modules/blake3-wasm": { |
| + | "version": "2.1.5", |
| + | "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", |
| + | "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/color": { |
| + | "version": "4.2.3", |
| + | "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", |
| + | "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "color-convert": "^2.0.1", |
| + | "color-string": "^1.9.0" |
| + | }, |
| + | "engines": { |
| + | "node": ">=12.5.0" |
| + | } |
| + | }, |
| + | "node_modules/color-convert": { |
| + | "version": "2.0.1", |
| + | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", |
| + | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "color-name": "~1.1.4" |
| + | }, |
| + | "engines": { |
| + | "node": ">=7.0.0" |
| + | } |
| + | }, |
| + | "node_modules/color-name": { |
| + | "version": "1.1.4", |
| + | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", |
| + | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/color-string": { |
| + | "version": "1.9.1", |
| + | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", |
| + | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "color-name": "^1.0.0", |
| + | "simple-swizzle": "^0.2.2" |
| + | } |
| + | }, |
| + | "node_modules/cookie": { |
| + | "version": "1.0.2", |
| + | "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", |
| + | "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=18" |
| + | } |
| + | }, |
| + | "node_modules/defu": { |
| + | "version": "6.1.4", |
| + | "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", |
| + | "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/detect-libc": { |
| + | "version": "2.0.4", |
| + | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", |
| + | "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", |
| + | "dev": true, |
| + | "license": "Apache-2.0", |
| + | "engines": { |
| + | "node": ">=8" |
| + | } |
| + | }, |
| + | "node_modules/error-stack-parser-es": { |
| + | "version": "1.0.5", |
| + | "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", |
| + | "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/antfu" |
| + | } |
| + | }, |
| + | "node_modules/esbuild": { |
| + | "version": "0.25.4", |
| + | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", |
| + | "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", |
| + | "dev": true, |
| + | "hasInstallScript": true, |
| + | "license": "MIT", |
| + | "bin": { |
| + | "esbuild": "bin/esbuild" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@esbuild/aix-ppc64": "0.25.4", |
| + | "@esbuild/android-arm": "0.25.4", |
| + | "@esbuild/android-arm64": "0.25.4", |
| + | "@esbuild/android-x64": "0.25.4", |
| + | "@esbuild/darwin-arm64": "0.25.4", |
| + | "@esbuild/darwin-x64": "0.25.4", |
| + | "@esbuild/freebsd-arm64": "0.25.4", |
| + | "@esbuild/freebsd-x64": "0.25.4", |
| + | "@esbuild/linux-arm": "0.25.4", |
| + | "@esbuild/linux-arm64": "0.25.4", |
| + | "@esbuild/linux-ia32": "0.25.4", |
| + | "@esbuild/linux-loong64": "0.25.4", |
| + | "@esbuild/linux-mips64el": "0.25.4", |
| + | "@esbuild/linux-ppc64": "0.25.4", |
| + | "@esbuild/linux-riscv64": "0.25.4", |
| + | "@esbuild/linux-s390x": "0.25.4", |
| + | "@esbuild/linux-x64": "0.25.4", |
| + | "@esbuild/netbsd-arm64": "0.25.4", |
| + | "@esbuild/netbsd-x64": "0.25.4", |
| + | "@esbuild/openbsd-arm64": "0.25.4", |
| + | "@esbuild/openbsd-x64": "0.25.4", |
| + | "@esbuild/sunos-x64": "0.25.4", |
| + | "@esbuild/win32-arm64": "0.25.4", |
| + | "@esbuild/win32-ia32": "0.25.4", |
| + | "@esbuild/win32-x64": "0.25.4" |
| + | } |
| + | }, |
| + | "node_modules/exit-hook": { |
| + | "version": "2.2.1", |
| + | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", |
| + | "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=6" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/sindresorhus" |
| + | } |
| + | }, |
| + | "node_modules/exsolve": { |
| + | "version": "1.0.7", |
| + | "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", |
| + | "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/fsevents": { |
| + | "version": "2.3.3", |
| + | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", |
| + | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", |
| + | "dev": true, |
| + | "hasInstallScript": true, |
| + | "license": "MIT", |
| + | "optional": true, |
| + | "os": [ |
| + | "darwin" |
| + | ], |
| + | "engines": { |
| + | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
| + | } |
| + | }, |
| + | "node_modules/glob-to-regexp": { |
| + | "version": "0.4.1", |
| + | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", |
| + | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", |
| + | "dev": true, |
| + | "license": "BSD-2-Clause" |
| + | }, |
| + | "node_modules/is-arrayish": { |
| + | "version": "0.3.2", |
| + | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", |
| + | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/kleur": { |
| + | "version": "4.1.5", |
| + | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", |
| + | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=6" |
| + | } |
| + | }, |
| + | "node_modules/mime": { |
| + | "version": "3.0.0", |
| + | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", |
| + | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "bin": { |
| + | "mime": "cli.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=10.0.0" |
| + | } |
| + | }, |
| + | "node_modules/miniflare": { |
| + | "version": "4.20250816.1", |
| + | "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.1.tgz", |
| + | "integrity": "sha512-2X8yMy5wWw0dF1pNU4kztzZgp0jWv2KMqAOOb2FeQ/b11yck4aczmYHi7UYD3uyOgtj8WFhwG/KdRWAaATTtRA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@cspotcode/source-map-support": "0.8.1", |
| + | "acorn": "8.14.0", |
| + | "acorn-walk": "8.3.2", |
| + | "exit-hook": "2.2.1", |
| + | "glob-to-regexp": "0.4.1", |
| + | "sharp": "^0.33.5", |
| + | "stoppable": "1.1.0", |
| + | "undici": "^7.10.0", |
| + | "workerd": "1.20250816.0", |
| + | "ws": "8.18.0", |
| + | "youch": "4.1.0-beta.10", |
| + | "zod": "3.22.3" |
| + | }, |
| + | "bin": { |
| + | "miniflare": "bootstrap.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18.0.0" |
| + | } |
| + | }, |
| + | "node_modules/ohash": { |
| + | "version": "2.0.11", |
| + | "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", |
| + | "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/path-to-regexp": { |
| + | "version": "6.3.0", |
| + | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", |
| + | "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/pathe": { |
| + | "version": "2.0.3", |
| + | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", |
| + | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/semver": { |
| + | "version": "7.7.2", |
| + | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", |
| + | "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", |
| + | "dev": true, |
| + | "license": "ISC", |
| + | "bin": { |
| + | "semver": "bin/semver.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=10" |
| + | } |
| + | }, |
| + | "node_modules/sharp": { |
| + | "version": "0.33.5", |
| + | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", |
| + | "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", |
| + | "dev": true, |
| + | "hasInstallScript": true, |
| + | "license": "Apache-2.0", |
| + | "dependencies": { |
| + | "color": "^4.2.3", |
| + | "detect-libc": "^2.0.3", |
| + | "semver": "^7.6.3" |
| + | }, |
| + | "engines": { |
| + | "node": "^18.17.0 || ^20.3.0 || >=21.0.0" |
| + | }, |
| + | "funding": { |
| + | "url": "https://opencollective.com/libvips" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@img/sharp-darwin-arm64": "0.33.5", |
| + | "@img/sharp-darwin-x64": "0.33.5", |
| + | "@img/sharp-libvips-darwin-arm64": "1.0.4", |
| + | "@img/sharp-libvips-darwin-x64": "1.0.4", |
| + | "@img/sharp-libvips-linux-arm": "1.0.5", |
| + | "@img/sharp-libvips-linux-arm64": "1.0.4", |
| + | "@img/sharp-libvips-linux-s390x": "1.0.4", |
| + | "@img/sharp-libvips-linux-x64": "1.0.4", |
| + | "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", |
| + | "@img/sharp-libvips-linuxmusl-x64": "1.0.4", |
| + | "@img/sharp-linux-arm": "0.33.5", |
| + | "@img/sharp-linux-arm64": "0.33.5", |
| + | "@img/sharp-linux-s390x": "0.33.5", |
| + | "@img/sharp-linux-x64": "0.33.5", |
| + | "@img/sharp-linuxmusl-arm64": "0.33.5", |
| + | "@img/sharp-linuxmusl-x64": "0.33.5", |
| + | "@img/sharp-wasm32": "0.33.5", |
| + | "@img/sharp-win32-ia32": "0.33.5", |
| + | "@img/sharp-win32-x64": "0.33.5" |
| + | } |
| + | }, |
| + | "node_modules/simple-swizzle": { |
| + | "version": "0.2.2", |
| + | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", |
| + | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "is-arrayish": "^0.3.1" |
| + | } |
| + | }, |
| + | "node_modules/stoppable": { |
| + | "version": "1.1.0", |
| + | "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", |
| + | "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=4", |
| + | "npm": ">=6" |
| + | } |
| + | }, |
| + | "node_modules/supports-color": { |
| + | "version": "10.2.0", |
| + | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.0.tgz", |
| + | "integrity": "sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=18" |
| + | }, |
| + | "funding": { |
| + | "url": "https://github.com/chalk/supports-color?sponsor=1" |
| + | } |
| + | }, |
| + | "node_modules/tslib": { |
| + | "version": "2.8.1", |
| + | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", |
| + | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", |
| + | "dev": true, |
| + | "license": "0BSD", |
| + | "optional": true |
| + | }, |
| + | "node_modules/ufo": { |
| + | "version": "1.6.1", |
| + | "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", |
| + | "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", |
| + | "dev": true, |
| + | "license": "MIT" |
| + | }, |
| + | "node_modules/undici": { |
| + | "version": "7.15.0", |
| + | "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz", |
| + | "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=20.18.1" |
| + | } |
| + | }, |
| + | "node_modules/unenv": { |
| + | "version": "2.0.0-rc.19", |
| + | "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", |
| + | "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "defu": "^6.1.4", |
| + | "exsolve": "^1.0.7", |
| + | "ohash": "^2.0.11", |
| + | "pathe": "^2.0.3", |
| + | "ufo": "^1.6.1" |
| + | } |
| + | }, |
| + | "node_modules/workerd": { |
| + | "version": "1.20250816.0", |
| + | "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", |
| + | "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", |
| + | "dev": true, |
| + | "hasInstallScript": true, |
| + | "license": "Apache-2.0", |
| + | "bin": { |
| + | "workerd": "bin/workerd" |
| + | }, |
| + | "engines": { |
| + | "node": ">=16" |
| + | }, |
| + | "optionalDependencies": { |
| + | "@cloudflare/workerd-darwin-64": "1.20250816.0", |
| + | "@cloudflare/workerd-darwin-arm64": "1.20250816.0", |
| + | "@cloudflare/workerd-linux-64": "1.20250816.0", |
| + | "@cloudflare/workerd-linux-arm64": "1.20250816.0", |
| + | "@cloudflare/workerd-windows-64": "1.20250816.0" |
| + | } |
| + | }, |
| + | "node_modules/wrangler": { |
| + | "version": "4.32.0", |
| + | "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.32.0.tgz", |
| + | "integrity": "sha512-q7TRSavBW3Eg3pp4rxqKJwSK+u/ieFOBdNvUsq1P1EMmyj3//tN/iXDokFak+dkW0vDYjsVG3PfOfHxU92OS6w==", |
| + | "dev": true, |
| + | "license": "MIT OR Apache-2.0", |
| + | "dependencies": { |
| + | "@cloudflare/kv-asset-handler": "0.4.0", |
| + | "@cloudflare/unenv-preset": "2.6.2", |
| + | "blake3-wasm": "2.1.5", |
| + | "esbuild": "0.25.4", |
| + | "miniflare": "4.20250816.1", |
| + | "path-to-regexp": "6.3.0", |
| + | "unenv": "2.0.0-rc.19", |
| + | "workerd": "1.20250816.0" |
| + | }, |
| + | "bin": { |
| + | "wrangler": "bin/wrangler.js", |
| + | "wrangler2": "bin/wrangler.js" |
| + | }, |
| + | "engines": { |
| + | "node": ">=18.0.0" |
| + | }, |
| + | "optionalDependencies": { |
| + | "fsevents": "~2.3.2" |
| + | }, |
| + | "peerDependencies": { |
| + | "@cloudflare/workers-types": "^4.20250816.0" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "@cloudflare/workers-types": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/ws": { |
| + | "version": "8.18.0", |
| + | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", |
| + | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "engines": { |
| + | "node": ">=10.0.0" |
| + | }, |
| + | "peerDependencies": { |
| + | "bufferutil": "^4.0.1", |
| + | "utf-8-validate": ">=5.0.2" |
| + | }, |
| + | "peerDependenciesMeta": { |
| + | "bufferutil": { |
| + | "optional": true |
| + | }, |
| + | "utf-8-validate": { |
| + | "optional": true |
| + | } |
| + | } |
| + | }, |
| + | "node_modules/youch": { |
| + | "version": "4.1.0-beta.10", |
| + | "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", |
| + | "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@poppinss/colors": "^4.1.5", |
| + | "@poppinss/dumper": "^0.6.4", |
| + | "@speed-highlight/core": "^1.2.7", |
| + | "cookie": "^1.0.2", |
| + | "youch-core": "^0.3.3" |
| + | } |
| + | }, |
| + | "node_modules/youch-core": { |
| + | "version": "0.3.3", |
| + | "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", |
| + | "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "dependencies": { |
| + | "@poppinss/exception": "^1.2.2", |
| + | "error-stack-parser-es": "^1.0.5" |
| + | } |
| + | }, |
| + | "node_modules/zod": { |
| + | "version": "3.22.3", |
| + | "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", |
| + | "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", |
| + | "dev": true, |
| + | "license": "MIT", |
| + | "funding": { |
| + | "url": "https://github.com/sponsors/colinhacks" |
| + | } |
| + | } |
| + | } |
| + | } |
package.json
+19
-0
| @@ | @@ -0,0 +1,19 @@ |
| + | { |
| + | "name": "qrurl", |
| + | "version": "1.0.0", |
| + | "main": "index.js", |
| + | "scripts": { |
| + | "dev": "wrangler dev", |
| + | "deploy": "wrangler deploy", |
| + | "test": "echo \"Error: no test specified\" && exit 1", |
| + | "db:init": "wrangler d1 execute qrurl-db --local --file=./schema/schema.sql", |
| + | "db:init:remote": "wrangler d1 execute qrurl-db --file=./schema/schema.sql" |
| + | }, |
| + | "keywords": [], |
| + | "author": "", |
| + | "license": "ISC", |
| + | "description": "", |
| + | "devDependencies": { |
| + | "wrangler": "^4.32.0" |
| + | } |
| + | } |
schema/schema.sql
+63
-0
| @@ | @@ -0,0 +1,63 @@ |
| + | -- Users table |
| + | CREATE TABLE IF NOT EXISTS users ( |
| + | id TEXT PRIMARY KEY, |
| + | email TEXT UNIQUE NOT NULL, |
| + | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| + | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| + | ); |
| + | |
| + | -- Authorized emails (whitelist) |
| + | CREATE TABLE IF NOT EXISTS authorized_emails ( |
| + | email TEXT PRIMARY KEY, |
| + | authorized BOOLEAN DEFAULT true, |
| + | added_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| + | ); |
| + | |
| + | -- URL entries |
| + | CREATE TABLE IF NOT EXISTS entries ( |
| + | id TEXT PRIMARY KEY, |
| + | user_id TEXT NOT NULL, |
| + | name TEXT NOT NULL, |
| + | original_url TEXT NOT NULL, |
| + | slug TEXT UNIQUE NOT NULL, |
| + | logo_url TEXT, |
| + | qr_code_url TEXT, |
| + | click_count INTEGER DEFAULT 0, |
| + | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| + | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| + | FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE |
| + | ); |
| + | |
| + | -- Create index for faster slug lookups |
| + | CREATE INDEX IF NOT EXISTS idx_entries_slug ON entries(slug); |
| + | CREATE INDEX IF NOT EXISTS idx_entries_user_id ON entries(user_id); |
| + | |
| + | -- Analytics |
| + | CREATE TABLE IF NOT EXISTS analytics ( |
| + | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| + | entry_id TEXT NOT NULL, |
| + | timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, |
| + | ip_hash TEXT, |
| + | user_agent TEXT, |
| + | referer TEXT, |
| + | country TEXT, |
| + | city TEXT, |
| + | FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE |
| + | ); |
| + | |
| + | -- Create index for analytics queries |
| + | CREATE INDEX IF NOT EXISTS idx_analytics_entry_id ON analytics(entry_id); |
| + | CREATE INDEX IF NOT EXISTS idx_analytics_timestamp ON analytics(timestamp); |
| + | |
| + | -- Magic link tokens |
| + | CREATE TABLE IF NOT EXISTS auth_tokens ( |
| + | token TEXT PRIMARY KEY, |
| + | email TEXT NOT NULL, |
| + | expires_at DATETIME NOT NULL, |
| + | used BOOLEAN DEFAULT false, |
| + | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| + | ); |
| + | |
| + | -- Create index for token lookups |
| + | CREATE INDEX IF NOT EXISTS idx_auth_tokens_email ON auth_tokens(email); |
| + | CREATE INDEX IF NOT EXISTS idx_auth_tokens_expires_at ON auth_tokens(expires_at); |
| \ No newline at end of file | |
src/index.js
+49
-0
| @@ | @@ -0,0 +1,49 @@ |
| + | import { Router } from './router'; |
| + | import { handleRedirect } from './routes/redirect'; |
| + | import { authRoutes } from './routes/auth'; |
| + | import { apiRoutes } from './routes/api'; |
| + | import { applyMiddleware } from './middleware'; |
| + | import { handleError } from './utils/errors'; |
| + | |
| + | export default { |
| + | async fetch(request, env, ctx) { |
| + | try { |
| + | const router = new Router(); |
| + | |
| + | // Health check |
| + | router.get('/health', () => { |
| + | return new Response(JSON.stringify({ |
| + | status: 'ok', |
| + | timestamp: new Date().toISOString() |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | }); |
| + | |
| + | // API routes |
| + | router.all('/api/auth/*', authRoutes); |
| + | router.all('/api/*', (request, env, ctx) => |
| + | applyMiddleware(request, env, ctx, apiRoutes) |
| + | ); |
| + | |
| + | // Redirect handler (must be last) |
| + | router.get('/:slug', handleRedirect); |
| + | |
| + | // Default route |
| + | router.all('*', () => { |
| + | return new Response(JSON.stringify({ |
| + | error: 'Not Found', |
| + | message: 'The requested resource was not found' |
| + | }), { |
| + | status: 404, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | }); |
| + | |
| + | return await router.handle(request, env, ctx); |
| + | } catch (error) { |
| + | return handleError(error); |
| + | } |
| + | } |
| + | }; |
| \ No newline at end of file | |
db.js b/src/lib/db.js
+164
-0
| @@ | @@ -0,0 +1,164 @@ |
| + | import { NotFoundError, ValidationError } from '../utils/errors'; |
| + | |
| + | export class Database { |
| + | constructor(db) { |
| + | this.db = db; |
| + | } |
| + | |
| + | // User operations |
| + | async createUser(id, email) { |
| + | const stmt = this.db.prepare( |
| + | 'INSERT INTO users (id, email) VALUES (?, ?)' |
| + | ); |
| + | return await stmt.bind(id, email).run(); |
| + | } |
| + | |
| + | async getUserByEmail(email) { |
| + | const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?'); |
| + | return await stmt.bind(email).first(); |
| + | } |
| + | |
| + | async getUserById(id) { |
| + | const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?'); |
| + | return await stmt.bind(id).first(); |
| + | } |
| + | |
| + | // Authorization operations |
| + | async isEmailAuthorized(email) { |
| + | const stmt = this.db.prepare( |
| + | 'SELECT * FROM authorized_emails WHERE email = ? AND authorized = true' |
| + | ); |
| + | const result = await stmt.bind(email).first(); |
| + | return !!result; |
| + | } |
| + | |
| + | async addAuthorizedEmail(email) { |
| + | const stmt = this.db.prepare( |
| + | 'INSERT OR REPLACE INTO authorized_emails (email) VALUES (?)' |
| + | ); |
| + | return await stmt.bind(email).run(); |
| + | } |
| + | |
| + | // Entry operations |
| + | async createEntry(data) { |
| + | const { id, userId, name, originalUrl, slug, logoUrl } = data; |
| + | const stmt = this.db.prepare( |
| + | `INSERT INTO entries (id, user_id, name, original_url, slug, logo_url) |
| + | VALUES (?, ?, ?, ?, ?, ?)` |
| + | ); |
| + | return await stmt.bind(id, userId, name, originalUrl, slug, logoUrl).run(); |
| + | } |
| + | |
| + | async getEntryBySlug(slug) { |
| + | const stmt = this.db.prepare('SELECT * FROM entries WHERE slug = ?'); |
| + | return await stmt.bind(slug).first(); |
| + | } |
| + | |
| + | async getEntryById(id) { |
| + | const stmt = this.db.prepare('SELECT * FROM entries WHERE id = ?'); |
| + | return await stmt.bind(id).first(); |
| + | } |
| + | |
| + | async getUserEntries(userId, limit = 50, offset = 0) { |
| + | const stmt = this.db.prepare( |
| + | `SELECT * FROM entries |
| + | WHERE user_id = ? |
| + | ORDER BY created_at DESC |
| + | LIMIT ? OFFSET ?` |
| + | ); |
| + | const result = await stmt.bind(userId, limit, offset).all(); |
| + | return result.results; |
| + | } |
| + | |
| + | async updateEntry(id, updates) { |
| + | const fields = []; |
| + | const values = []; |
| + | |
| + | for (const [key, value] of Object.entries(updates)) { |
| + | // Convert camelCase to snake_case |
| + | const dbField = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); |
| + | fields.push(`${dbField} = ?`); |
| + | values.push(value); |
| + | } |
| + | |
| + | values.push(id); |
| + | const stmt = this.db.prepare( |
| + | `UPDATE entries |
| + | SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP |
| + | WHERE id = ?` |
| + | ); |
| + | return await stmt.bind(...values).run(); |
| + | } |
| + | |
| + | async deleteEntry(id) { |
| + | const stmt = this.db.prepare('DELETE FROM entries WHERE id = ?'); |
| + | return await stmt.bind(id).run(); |
| + | } |
| + | |
| + | async incrementClickCount(slug) { |
| + | const stmt = this.db.prepare( |
| + | 'UPDATE entries SET click_count = click_count + 1 WHERE slug = ?' |
| + | ); |
| + | return await stmt.bind(slug).run(); |
| + | } |
| + | |
| + | // Analytics operations |
| + | async recordAnalytics(data) { |
| + | const { entryId, ipHash, userAgent, referer, country, city } = data; |
| + | const stmt = this.db.prepare( |
| + | `INSERT INTO analytics (entry_id, ip_hash, user_agent, referer, country, city) |
| + | VALUES (?, ?, ?, ?, ?, ?)` |
| + | ); |
| + | return await stmt.bind(entryId, ipHash, userAgent, referer, country, city).run(); |
| + | } |
| + | |
| + | async getEntryAnalytics(entryId, days = 30) { |
| + | const stmt = this.db.prepare( |
| + | `SELECT |
| + | COUNT(*) as total_clicks, |
| + | COUNT(DISTINCT ip_hash) as unique_visitors, |
| + | DATE(timestamp) as date, |
| + | country, |
| + | COUNT(*) as clicks |
| + | FROM analytics |
| + | WHERE entry_id = ? |
| + | AND timestamp > datetime('now', '-' || ? || ' days') |
| + | GROUP BY DATE(timestamp), country |
| + | ORDER BY date DESC` |
| + | ); |
| + | const result = await stmt.bind(entryId, days).all(); |
| + | return result.results; |
| + | } |
| + | |
| + | // Auth token operations |
| + | async createAuthToken(token, email, expiresAt) { |
| + | const stmt = this.db.prepare( |
| + | 'INSERT INTO auth_tokens (token, email, expires_at) VALUES (?, ?, ?)' |
| + | ); |
| + | return await stmt.bind(token, email, expiresAt).run(); |
| + | } |
| + | |
| + | async getAuthToken(token) { |
| + | const stmt = this.db.prepare( |
| + | `SELECT * FROM auth_tokens |
| + | WHERE token = ? |
| + | AND expires_at > datetime('now') |
| + | AND used = false` |
| + | ); |
| + | return await stmt.bind(token).first(); |
| + | } |
| + | |
| + | async markTokenUsed(token) { |
| + | const stmt = this.db.prepare( |
| + | 'UPDATE auth_tokens SET used = true WHERE token = ?' |
| + | ); |
| + | return await stmt.bind(token).run(); |
| + | } |
| + | |
| + | async cleanupExpiredTokens() { |
| + | const stmt = this.db.prepare( |
| + | 'DELETE FROM auth_tokens WHERE expires_at < datetime("now")' |
| + | ); |
| + | return await stmt.run(); |
| + | } |
| + | } |
| \ No newline at end of file | |
src/middleware/auth.js
+119
-0
| @@ | @@ -0,0 +1,119 @@ |
| + | import { AuthError } from '../utils/errors'; |
| + | |
| + | export async function verifyAuth(request, env) { |
| + | // Skip auth for public endpoints |
| + | const url = new URL(request.url); |
| + | const publicPaths = ['/health', '/api/health', '/api/auth/request', '/api/auth/verify']; |
| + | |
| + | // Skip auth for non-API paths (like redirects) or public API paths |
| + | if (!url.pathname.startsWith('/api') || publicPaths.includes(url.pathname)) { |
| + | return null; |
| + | } |
| + | |
| + | // Get token from Authorization header |
| + | const authHeader = request.headers.get('Authorization'); |
| + | if (!authHeader || !authHeader.startsWith('Bearer ')) { |
| + | return new Response(JSON.stringify({ error: 'Unauthorized' }), { |
| + | status: 401, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | |
| + | const token = authHeader.slice(7); |
| + | |
| + | try { |
| + | // Verify JWT token |
| + | const payload = await verifyJWT(token, env.JWT_SECRET); |
| + | |
| + | // Add user info to request |
| + | request.user = { |
| + | id: payload.sub, |
| + | email: payload.email |
| + | }; |
| + | |
| + | return null; // Continue to handler |
| + | } catch (error) { |
| + | return new Response(JSON.stringify({ error: 'Invalid token' }), { |
| + | status: 401, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | export async function createJWT(payload, secret, expiresIn = '7d') { |
| + | const encoder = new TextEncoder(); |
| + | |
| + | const header = { |
| + | alg: 'HS256', |
| + | typ: 'JWT' |
| + | }; |
| + | |
| + | const now = Math.floor(Date.now() / 1000); |
| + | const exp = expiresIn === '7d' ? now + (7 * 24 * 60 * 60) : now + 3600; |
| + | |
| + | const tokenPayload = { |
| + | ...payload, |
| + | iat: now, |
| + | exp: exp |
| + | }; |
| + | |
| + | const encodedHeader = btoa(JSON.stringify(header)).replace(/=/g, ''); |
| + | const encodedPayload = btoa(JSON.stringify(tokenPayload)).replace(/=/g, ''); |
| + | |
| + | const message = `${encodedHeader}.${encodedPayload}`; |
| + | const key = await crypto.subtle.importKey( |
| + | 'raw', |
| + | encoder.encode(secret), |
| + | { name: 'HMAC', hash: 'SHA-256' }, |
| + | false, |
| + | ['sign'] |
| + | ); |
| + | |
| + | const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message)); |
| + | const encodedSignature = btoa(String.fromCharCode(...new Uint8Array(signature))) |
| + | .replace(/=/g, '') |
| + | .replace(/\+/g, '-') |
| + | .replace(/\//g, '_'); |
| + | |
| + | return `${message}.${encodedSignature}`; |
| + | } |
| + | |
| + | export async function verifyJWT(token, secret) { |
| + | const encoder = new TextEncoder(); |
| + | const [header, payload, signature] = token.split('.'); |
| + | |
| + | if (!header || !payload || !signature) { |
| + | throw new Error('Invalid token format'); |
| + | } |
| + | |
| + | const key = await crypto.subtle.importKey( |
| + | 'raw', |
| + | encoder.encode(secret), |
| + | { name: 'HMAC', hash: 'SHA-256' }, |
| + | false, |
| + | ['verify'] |
| + | ); |
| + | |
| + | const message = `${header}.${payload}`; |
| + | const signatureBytes = Uint8Array.from(atob(signature.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); |
| + | |
| + | const valid = await crypto.subtle.verify( |
| + | 'HMAC', |
| + | key, |
| + | signatureBytes, |
| + | encoder.encode(message) |
| + | ); |
| + | |
| + | if (!valid) { |
| + | throw new Error('Invalid signature'); |
| + | } |
| + | |
| + | const decodedPayload = JSON.parse(atob(payload)); |
| + | |
| + | // Check expiration |
| + | if (decodedPayload.exp && decodedPayload.exp < Math.floor(Date.now() / 1000)) { |
| + | throw new Error('Token expired'); |
| + | } |
| + | |
| + | return decodedPayload; |
| + | } |
| \ No newline at end of file | |
src/middleware/cors.js
+29
-0
| @@ | @@ -0,0 +1,29 @@ |
| + | export function cors(request, env) { |
| + | // Handle preflight requests |
| + | if (request.method === 'OPTIONS') { |
| + | return new Response(null, { |
| + | status: 204, |
| + | headers: { |
| + | 'Access-Control-Allow-Origin': env.FRONTEND_URL || '*', |
| + | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', |
| + | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', |
| + | 'Access-Control-Max-Age': '86400', |
| + | } |
| + | }); |
| + | } |
| + | |
| + | return null; |
| + | } |
| + | |
| + | export function addCorsHeaders(response, env) { |
| + | const headers = new Headers(response.headers); |
| + | headers.set('Access-Control-Allow-Origin', env.FRONTEND_URL || '*'); |
| + | headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); |
| + | headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); |
| + | |
| + | return new Response(response.body, { |
| + | status: response.status, |
| + | statusText: response.statusText, |
| + | headers |
| + | }); |
| + | } |
| \ No newline at end of file | |
src/middleware/index.js
+20
-0
| @@ | @@ -0,0 +1,20 @@ |
| + | import { verifyAuth } from './auth'; |
| + | import { rateLimit } from './rateLimit'; |
| + | import { cors } from './cors'; |
| + | |
| + | export async function applyMiddleware(request, env, ctx, handler) { |
| + | // Apply CORS |
| + | const corsResponse = cors(request, env); |
| + | if (corsResponse) return corsResponse; |
| + | |
| + | // Apply rate limiting |
| + | const rateLimitResponse = await rateLimit(request, env); |
| + | if (rateLimitResponse) return rateLimitResponse; |
| + | |
| + | // Apply auth middleware for protected routes |
| + | const authResponse = await verifyAuth(request, env); |
| + | if (authResponse) return authResponse; |
| + | |
| + | // Call the actual handler |
| + | return handler(request, env, ctx); |
| + | } |
| \ No newline at end of file | |
src/middleware/rateLimit.js
+61
-0
| @@ | @@ -0,0 +1,61 @@ |
| + | export async function rateLimit(request, env) { |
| + | // Get client IP |
| + | const ip = request.headers.get('CF-Connecting-IP') || |
| + | request.headers.get('X-Forwarded-For') || |
| + | 'unknown'; |
| + | |
| + | // Skip rate limiting for health checks |
| + | const url = new URL(request.url); |
| + | if (url.pathname === '/health') { |
| + | return null; |
| + | } |
| + | |
| + | // Simple rate limiting using KV |
| + | if (env.CACHE) { |
| + | const key = `ratelimit:${ip}`; |
| + | const now = Date.now(); |
| + | const window = 60000; // 1 minute window |
| + | const limit = 60; // 60 requests per minute |
| + | |
| + | const data = await env.CACHE.get(key, 'json'); |
| + | |
| + | if (data) { |
| + | // Reset if window has passed |
| + | if (now - data.start > window) { |
| + | await env.CACHE.put(key, JSON.stringify({ |
| + | start: now, |
| + | count: 1 |
| + | }), { expirationTtl: 60 }); |
| + | return null; |
| + | } |
| + | |
| + | // Check if limit exceeded |
| + | if (data.count >= limit) { |
| + | return new Response(JSON.stringify({ |
| + | error: 'Too many requests', |
| + | retryAfter: Math.ceil((data.start + window - now) / 1000) |
| + | }), { |
| + | status: 429, |
| + | headers: { |
| + | 'Content-Type': 'application/json', |
| + | 'Retry-After': String(Math.ceil((data.start + window - now) / 1000)) |
| + | } |
| + | }); |
| + | } |
| + | |
| + | // Increment counter |
| + | await env.CACHE.put(key, JSON.stringify({ |
| + | ...data, |
| + | count: data.count + 1 |
| + | }), { expirationTtl: 60 }); |
| + | } else { |
| + | // First request |
| + | await env.CACHE.put(key, JSON.stringify({ |
| + | start: now, |
| + | count: 1 |
| + | }), { expirationTtl: 60 }); |
| + | } |
| + | } |
| + | |
| + | return null; |
| + | } |
| \ No newline at end of file | |
src/router.js
+82
-0
| @@ | @@ -0,0 +1,82 @@ |
| + | export class Router { |
| + | constructor() { |
| + | this.routes = []; |
| + | } |
| + | |
| + | addRoute(method, pattern, handler) { |
| + | this.routes.push({ method, pattern, handler }); |
| + | } |
| + | |
| + | get(pattern, handler) { |
| + | this.addRoute('GET', pattern, handler); |
| + | } |
| + | |
| + | post(pattern, handler) { |
| + | this.addRoute('POST', pattern, handler); |
| + | } |
| + | |
| + | put(pattern, handler) { |
| + | this.addRoute('PUT', pattern, handler); |
| + | } |
| + | |
| + | delete(pattern, handler) { |
| + | this.addRoute('DELETE', pattern, handler); |
| + | } |
| + | |
| + | all(pattern, handler) { |
| + | this.addRoute('*', pattern, handler); |
| + | } |
| + | |
| + | async handle(request, env, ctx) { |
| + | const url = new URL(request.url); |
| + | const path = url.pathname; |
| + | const method = request.method; |
| + | |
| + | for (const route of this.routes) { |
| + | if (route.method !== '*' && route.method !== method) continue; |
| + | |
| + | const params = this.matchRoute(route.pattern, path); |
| + | if (params) { |
| + | request.params = params; |
| + | request.query = Object.fromEntries(url.searchParams); |
| + | return await route.handler(request, env, ctx); |
| + | } |
| + | } |
| + | |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | |
| + | matchRoute(pattern, path) { |
| + | if (pattern === '*') return {}; |
| + | |
| + | // Handle wildcard patterns like /api/* |
| + | if (pattern.endsWith('/*')) { |
| + | const base = pattern.slice(0, -2); |
| + | if (path.startsWith(base + '/') || path === base) { |
| + | return {}; |
| + | } |
| + | return null; |
| + | } |
| + | |
| + | // Handle exact matches |
| + | if (pattern === path) return {}; |
| + | |
| + | // Handle parameterized routes like /:slug |
| + | const patternParts = pattern.split('/'); |
| + | const pathParts = path.split('/'); |
| + | |
| + | if (patternParts.length !== pathParts.length) return null; |
| + | |
| + | const params = {}; |
| + | for (let i = 0; i < patternParts.length; i++) { |
| + | if (patternParts[i].startsWith(':')) { |
| + | const paramName = patternParts[i].slice(1); |
| + | params[paramName] = pathParts[i]; |
| + | } else if (patternParts[i] !== pathParts[i]) { |
| + | return null; |
| + | } |
| + | } |
| + | |
| + | return params; |
| + | } |
| + | } |
| \ No newline at end of file | |
src/routes/api.js
+322
-0
| @@ | @@ -0,0 +1,322 @@ |
| + | import { Database } from '../lib/db'; |
| + | import { generateSlug, validateCustomSlug } from '../utils/slug'; |
| + | import { validateUrl, sanitizeInput } from '../utils/validation'; |
| + | import { ValidationError, NotFoundError, AuthError } from '../utils/errors'; |
| + | |
| + | export async function apiRoutes(request, env, ctx) { |
| + | const url = new URL(request.url); |
| + | const path = url.pathname; |
| + | const method = request.method; |
| + | |
| + | // Entry routes |
| + | if (path === '/api/entries' && method === 'GET') { |
| + | return getUserEntries(request, env); |
| + | } |
| + | |
| + | if (path === '/api/entries' && method === 'POST') { |
| + | return createEntry(request, env); |
| + | } |
| + | |
| + | if (path.startsWith('/api/entries/') && method === 'GET') { |
| + | const id = path.split('/')[3]; |
| + | return getEntry(request, env, id); |
| + | } |
| + | |
| + | if (path.startsWith('/api/entries/') && method === 'PUT') { |
| + | const id = path.split('/')[3]; |
| + | return updateEntry(request, env, id); |
| + | } |
| + | |
| + | if (path.startsWith('/api/entries/') && method === 'DELETE') { |
| + | const id = path.split('/')[3]; |
| + | return deleteEntry(request, env, id); |
| + | } |
| + | |
| + | // Analytics routes |
| + | if (path.startsWith('/api/analytics/')) { |
| + | const id = path.split('/')[3]; |
| + | return getAnalytics(request, env, id); |
| + | } |
| + | |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | |
| + | async function getUserEntries(request, env) { |
| + | try { |
| + | const db = new Database(env.DB); |
| + | const url = new URL(request.url); |
| + | const limit = Math.min(parseInt(url.searchParams.get('limit') || '50'), 100); |
| + | const offset = parseInt(url.searchParams.get('offset') || '0'); |
| + | |
| + | const entries = await db.getUserEntries(request.user.id, limit, offset); |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | entries |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Get entries error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to fetch entries' |
| + | }), { |
| + | status: error.status || 500, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function createEntry(request, env) { |
| + | try { |
| + | const body = await request.json(); |
| + | const { name, url, customSlug, logoUrl } = body; |
| + | |
| + | // Validate inputs |
| + | if (!name || !url) { |
| + | throw new ValidationError('Name and URL are required'); |
| + | } |
| + | |
| + | validateUrl(url); |
| + | const sanitizedName = sanitizeInput(name); |
| + | |
| + | // Generate or validate slug |
| + | let slug; |
| + | if (customSlug) { |
| + | validateCustomSlug(customSlug); |
| + | slug = customSlug; |
| + | } else { |
| + | slug = generateSlug(); |
| + | } |
| + | |
| + | const db = new Database(env.DB); |
| + | |
| + | // Check if slug already exists |
| + | const existing = await db.getEntryBySlug(slug); |
| + | if (existing) { |
| + | // If custom slug, throw error |
| + | if (customSlug) { |
| + | throw new ValidationError('This slug is already in use'); |
| + | } |
| + | // Otherwise, generate a new one |
| + | slug = generateSlug(8); // Try with longer slug |
| + | } |
| + | |
| + | // Create entry |
| + | const entryId = crypto.randomUUID(); |
| + | await db.createEntry({ |
| + | id: entryId, |
| + | userId: request.user.id, |
| + | name: sanitizedName, |
| + | originalUrl: url, |
| + | slug, |
| + | logoUrl: logoUrl || null |
| + | }); |
| + | |
| + | // Clear cache for user entries |
| + | if (env.CACHE) { |
| + | await env.CACHE.delete(`user-entries:${request.user.id}`); |
| + | } |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | entry: { |
| + | id: entryId, |
| + | name: sanitizedName, |
| + | originalUrl: url, |
| + | slug, |
| + | shortUrl: `${env.BACKEND_URL}/${slug}`, |
| + | logoUrl: logoUrl || null |
| + | } |
| + | }), { |
| + | status: 201, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Create entry error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to create entry' |
| + | }), { |
| + | status: error.status || 400, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function getEntry(request, env, id) { |
| + | try { |
| + | const db = new Database(env.DB); |
| + | const entry = await db.getEntryById(id); |
| + | |
| + | if (!entry) { |
| + | throw new NotFoundError('Entry not found'); |
| + | } |
| + | |
| + | // Check ownership |
| + | if (entry.user_id !== request.user.id) { |
| + | throw new AuthError('You do not have permission to view this entry'); |
| + | } |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | entry |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Get entry error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to fetch entry' |
| + | }), { |
| + | status: error.status || 500, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function updateEntry(request, env, id) { |
| + | try { |
| + | const body = await request.json(); |
| + | const { name, url, logoUrl } = body; |
| + | |
| + | const db = new Database(env.DB); |
| + | const entry = await db.getEntryById(id); |
| + | |
| + | if (!entry) { |
| + | throw new NotFoundError('Entry not found'); |
| + | } |
| + | |
| + | // Check ownership |
| + | if (entry.user_id !== request.user.id) { |
| + | throw new AuthError('You do not have permission to update this entry'); |
| + | } |
| + | |
| + | const updates = {}; |
| + | |
| + | if (name !== undefined) { |
| + | updates.name = sanitizeInput(name); |
| + | } |
| + | |
| + | if (url !== undefined) { |
| + | validateUrl(url); |
| + | updates.originalUrl = url; |
| + | } |
| + | |
| + | if (logoUrl !== undefined) { |
| + | updates.logoUrl = logoUrl; |
| + | } |
| + | |
| + | if (Object.keys(updates).length > 0) { |
| + | await db.updateEntry(id, updates); |
| + | |
| + | // Clear cache |
| + | if (env.CACHE) { |
| + | await env.CACHE.delete(`entry:${entry.slug}`); |
| + | await env.CACHE.delete(`user-entries:${request.user.id}`); |
| + | } |
| + | } |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | message: 'Entry updated successfully' |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Update entry error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to update entry' |
| + | }), { |
| + | status: error.status || 400, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function deleteEntry(request, env, id) { |
| + | try { |
| + | const db = new Database(env.DB); |
| + | const entry = await db.getEntryById(id); |
| + | |
| + | if (!entry) { |
| + | throw new NotFoundError('Entry not found'); |
| + | } |
| + | |
| + | // Check ownership |
| + | if (entry.user_id !== request.user.id) { |
| + | throw new AuthError('You do not have permission to delete this entry'); |
| + | } |
| + | |
| + | await db.deleteEntry(id); |
| + | |
| + | // Clear cache |
| + | if (env.CACHE) { |
| + | await env.CACHE.delete(`entry:${entry.slug}`); |
| + | await env.CACHE.delete(`user-entries:${request.user.id}`); |
| + | } |
| + | |
| + | // TODO: Delete associated QR code from R2 |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | message: 'Entry deleted successfully' |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Delete entry error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to delete entry' |
| + | }), { |
| + | status: error.status || 500, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function getAnalytics(request, env, id) { |
| + | try { |
| + | const db = new Database(env.DB); |
| + | const entry = await db.getEntryById(id); |
| + | |
| + | if (!entry) { |
| + | throw new NotFoundError('Entry not found'); |
| + | } |
| + | |
| + | // Check ownership |
| + | if (entry.user_id !== request.user.id) { |
| + | throw new AuthError('You do not have permission to view analytics for this entry'); |
| + | } |
| + | |
| + | const url = new URL(request.url); |
| + | const days = parseInt(url.searchParams.get('days') || '30'); |
| + | |
| + | const analytics = await db.getEntryAnalytics(id, days); |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | entry: { |
| + | id: entry.id, |
| + | name: entry.name, |
| + | slug: entry.slug, |
| + | clickCount: entry.click_count |
| + | }, |
| + | analytics |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Get analytics error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to fetch analytics' |
| + | }), { |
| + | status: error.status || 500, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| \ No newline at end of file | |
src/routes/auth.js
+192
-0
| @@ | @@ -0,0 +1,192 @@ |
| + | import { Database } from '../lib/db'; |
| + | import { createJWT } from '../middleware/auth'; |
| + | import { validateEmail } from '../utils/validation'; |
| + | import { ValidationError, AuthError } from '../utils/errors'; |
| + | |
| + | export async function authRoutes(request, env, ctx) { |
| + | const url = new URL(request.url); |
| + | const path = url.pathname; |
| + | |
| + | if (path === '/api/auth/request' && request.method === 'POST') { |
| + | return requestMagicLink(request, env); |
| + | } |
| + | |
| + | if (path === '/api/auth/verify' && request.method === 'POST') { |
| + | return verifyMagicLink(request, env); |
| + | } |
| + | |
| + | if (path === '/api/auth/logout' && request.method === 'POST') { |
| + | return logout(request, env); |
| + | } |
| + | |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | |
| + | async function requestMagicLink(request, env) { |
| + | try { |
| + | const body = await request.json(); |
| + | const { email } = body; |
| + | |
| + | // Validate email |
| + | validateEmail(email); |
| + | |
| + | const db = new Database(env.DB); |
| + | |
| + | // Check if email is authorized |
| + | const authorized = await db.isEmailAuthorized(email); |
| + | if (!authorized) { |
| + | // Check environment variable whitelist as fallback |
| + | const authorizedEmails = env.AUTHORIZED_EMAILS?.split(',').map(e => e.trim()) || []; |
| + | if (!authorizedEmails.includes(email)) { |
| + | return new Response(JSON.stringify({ |
| + | error: 'This email is not authorized to use this service' |
| + | }), { |
| + | status: 403, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | // Add to database for future |
| + | await db.addAuthorizedEmail(email); |
| + | } |
| + | |
| + | // Generate secure token |
| + | const tokenBytes = new Uint8Array(32); |
| + | crypto.getRandomValues(tokenBytes); |
| + | const token = Array.from(tokenBytes, b => b.toString(16).padStart(2, '0')).join(''); |
| + | |
| + | // Set expiry to 15 minutes from now |
| + | const expiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString(); |
| + | |
| + | // Store token in database |
| + | await db.createAuthToken(token, email, expiresAt); |
| + | |
| + | // Send email (mock for now, integrate with email service) |
| + | const magicLink = `${env.FRONTEND_URL}/auth/verify?token=${token}`; |
| + | |
| + | // TODO: Integrate with actual email service |
| + | console.log(`Magic link for ${email}: ${magicLink}`); |
| + | |
| + | // In production, send actual email |
| + | if (env.EMAIL_API_KEY) { |
| + | await sendEmail(env, email, magicLink); |
| + | } |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | message: 'Magic link sent to your email' |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Magic link request error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to send magic link' |
| + | }), { |
| + | status: 400, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function verifyMagicLink(request, env) { |
| + | try { |
| + | const body = await request.json(); |
| + | const { token } = body; |
| + | |
| + | if (!token) { |
| + | throw new ValidationError('Token is required'); |
| + | } |
| + | |
| + | const db = new Database(env.DB); |
| + | |
| + | // Get token from database |
| + | const authToken = await db.getAuthToken(token); |
| + | if (!authToken) { |
| + | throw new AuthError('Invalid or expired token'); |
| + | } |
| + | |
| + | // Mark token as used |
| + | await db.markTokenUsed(token); |
| + | |
| + | // Get or create user |
| + | let user = await db.getUserByEmail(authToken.email); |
| + | if (!user) { |
| + | const userId = crypto.randomUUID(); |
| + | await db.createUser(userId, authToken.email); |
| + | user = { id: userId, email: authToken.email }; |
| + | } |
| + | |
| + | // Create JWT |
| + | const jwt = await createJWT({ |
| + | sub: user.id, |
| + | email: user.email |
| + | }, env.JWT_SECRET); |
| + | |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | token: jwt, |
| + | user: { |
| + | id: user.id, |
| + | email: user.email |
| + | } |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } catch (error) { |
| + | console.error('Magic link verification error:', error); |
| + | return new Response(JSON.stringify({ |
| + | error: error.message || 'Failed to verify token' |
| + | }), { |
| + | status: error.status || 400, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | } |
| + | |
| + | async function logout(request, env) { |
| + | // Since we're using JWTs, we can't invalidate them server-side |
| + | // Client should remove the token |
| + | return new Response(JSON.stringify({ |
| + | success: true, |
| + | message: 'Logged out successfully' |
| + | }), { |
| + | status: 200, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | |
| + | async function sendEmail(env, email, magicLink) { |
| + | // Example with Resend API |
| + | if (env.EMAIL_API_KEY && env.EMAIL_API_KEY.startsWith('re_')) { |
| + | const response = await fetch('https://api.resend.com/emails', { |
| + | method: 'POST', |
| + | headers: { |
| + | 'Authorization': `Bearer ${env.EMAIL_API_KEY}`, |
| + | 'Content-Type': 'application/json' |
| + | }, |
| + | body: JSON.stringify({ |
| + | from: 'QRurl <noreply@qrurl.app>', |
| + | to: email, |
| + | subject: 'Your QRurl Login Link', |
| + | html: ` |
| + | <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;"> |
| + | <h2>Login to QRurl</h2> |
| + | <p>Click the link below to log in to your QRurl account:</p> |
| + | <a href="${magicLink}" style="display: inline-block; padding: 12px 24px; background: #000; color: #fff; text-decoration: none; border-radius: 4px;"> |
| + | Log In |
| + | </a> |
| + | <p style="color: #666; font-size: 14px; margin-top: 20px;"> |
| + | This link expires in 15 minutes. If you didn't request this, please ignore this email. |
| + | </p> |
| + | </div> |
| + | ` |
| + | }) |
| + | }); |
| + | |
| + | if (!response.ok) { |
| + | throw new Error('Failed to send email'); |
| + | } |
| + | } |
| + | } |
| \ No newline at end of file | |
src/routes/redirect.js
+82
-0
| @@ | @@ -0,0 +1,82 @@ |
| + | import { Database } from '../lib/db'; |
| + | import { NotFoundError } from '../utils/errors'; |
| + | |
| + | export async function handleRedirect(request, env, ctx) { |
| + | const { slug } = request.params; |
| + | |
| + | if (!slug) { |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | |
| + | try { |
| + | const db = new Database(env.DB); |
| + | |
| + | // Try to get from cache first |
| + | let entry; |
| + | if (env.CACHE) { |
| + | const cached = await env.CACHE.get(`entry:${slug}`, 'json'); |
| + | if (cached) { |
| + | entry = cached; |
| + | } |
| + | } |
| + | |
| + | // If not in cache, get from database |
| + | if (!entry) { |
| + | entry = await db.getEntryBySlug(slug); |
| + | if (!entry) { |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | |
| + | // Cache for 5 minutes |
| + | if (env.CACHE) { |
| + | await env.CACHE.put(`entry:${slug}`, JSON.stringify(entry), { |
| + | expirationTtl: 300 |
| + | }); |
| + | } |
| + | } |
| + | |
| + | // Record analytics asynchronously |
| + | ctx.waitUntil(recordAnalytics(request, env, entry.id)); |
| + | |
| + | // Increment click count asynchronously |
| + | ctx.waitUntil(db.incrementClickCount(slug)); |
| + | |
| + | // Redirect to the original URL |
| + | return Response.redirect(entry.original_url, 301); |
| + | } catch (error) { |
| + | console.error('Redirect error:', error); |
| + | return new Response('Not Found', { status: 404 }); |
| + | } |
| + | } |
| + | |
| + | async function recordAnalytics(request, env, entryId) { |
| + | try { |
| + | const db = new Database(env.DB); |
| + | |
| + | // Get client info |
| + | const ip = request.headers.get('CF-Connecting-IP') || 'unknown'; |
| + | const userAgent = request.headers.get('User-Agent') || 'unknown'; |
| + | const referer = request.headers.get('Referer') || null; |
| + | const country = request.cf?.country || null; |
| + | const city = request.cf?.city || null; |
| + | |
| + | // Hash IP for privacy |
| + | const encoder = new TextEncoder(); |
| + | const data = encoder.encode(ip + env.JWT_SECRET); |
| + | const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
| + | const hashArray = Array.from(new Uint8Array(hashBuffer)); |
| + | const ipHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); |
| + | |
| + | await db.recordAnalytics({ |
| + | entryId, |
| + | ipHash: ipHash.substring(0, 16), // Use first 16 chars of hash |
| + | userAgent: userAgent.substring(0, 255), |
| + | referer: referer ? referer.substring(0, 255) : null, |
| + | country, |
| + | city |
| + | }); |
| + | } catch (error) { |
| + | console.error('Analytics error:', error); |
| + | // Don't throw - analytics shouldn't break redirects |
| + | } |
| + | } |
| \ No newline at end of file | |
src/utils/errors.js
+51
-0
| @@ | @@ -0,0 +1,51 @@ |
| + | export function handleError(error) { |
| + | console.error('Error:', error); |
| + | |
| + | const status = error.status || 500; |
| + | const message = error.message || 'Internal Server Error'; |
| + | |
| + | return new Response(JSON.stringify({ |
| + | error: true, |
| + | message: message, |
| + | ...(process.env.NODE_ENV === 'development' && { stack: error.stack }) |
| + | }), { |
| + | status, |
| + | headers: { 'Content-Type': 'application/json' } |
| + | }); |
| + | } |
| + | |
| + | export class AppError extends Error { |
| + | constructor(message, status = 500) { |
| + | super(message); |
| + | this.status = status; |
| + | this.name = 'AppError'; |
| + | } |
| + | } |
| + | |
| + | export class ValidationError extends AppError { |
| + | constructor(message) { |
| + | super(message, 400); |
| + | this.name = 'ValidationError'; |
| + | } |
| + | } |
| + | |
| + | export class AuthError extends AppError { |
| + | constructor(message = 'Unauthorized') { |
| + | super(message, 401); |
| + | this.name = 'AuthError'; |
| + | } |
| + | } |
| + | |
| + | export class NotFoundError extends AppError { |
| + | constructor(message = 'Resource not found') { |
| + | super(message, 404); |
| + | this.name = 'NotFoundError'; |
| + | } |
| + | } |
| + | |
| + | export class RateLimitError extends AppError { |
| + | constructor(message = 'Too many requests') { |
| + | super(message, 429); |
| + | this.name = 'RateLimitError'; |
| + | } |
| + | } |
| \ No newline at end of file | |
src/utils/slug.js
+31
-0
| @@ | @@ -0,0 +1,31 @@ |
| + | // Custom alphabet without confusing characters (no 0, O, I, l) |
| + | const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz'; |
| + | |
| + | export function generateSlug(length = 7) { |
| + | let slug = ''; |
| + | const randomValues = new Uint8Array(length); |
| + | crypto.getRandomValues(randomValues); |
| + | |
| + | for (let i = 0; i < length; i++) { |
| + | slug += ALPHABET[randomValues[i] % ALPHABET.length]; |
| + | } |
| + | |
| + | return slug; |
| + | } |
| + | |
| + | export function validateCustomSlug(slug) { |
| + | // Allow alphanumeric and hyphens, 3-50 characters |
| + | const pattern = /^[a-zA-Z0-9-]{3,50}$/; |
| + | |
| + | if (!pattern.test(slug)) { |
| + | throw new Error('Slug must be 3-50 characters and contain only letters, numbers, and hyphens'); |
| + | } |
| + | |
| + | // Don't allow reserved paths |
| + | const reserved = ['api', 'health', 'auth', 'admin', 'dashboard', 'login', 'logout']; |
| + | if (reserved.includes(slug.toLowerCase())) { |
| + | throw new Error('This slug is reserved'); |
| + | } |
| + | |
| + | return true; |
| + | } |
| \ No newline at end of file | |
src/utils/validation.js
+41
-0
| @@ | @@ -0,0 +1,41 @@ |
| + | export function validateUrl(url) { |
| + | try { |
| + | const parsed = new URL(url); |
| + | |
| + | // Only allow http and https protocols |
| + | if (!['http:', 'https:'].includes(parsed.protocol)) { |
| + | throw new Error('URL must use HTTP or HTTPS protocol'); |
| + | } |
| + | |
| + | // Prevent javascript: and data: URIs |
| + | if (url.toLowerCase().includes('javascript:') || url.toLowerCase().includes('data:')) { |
| + | throw new Error('Invalid URL protocol'); |
| + | } |
| + | |
| + | return true; |
| + | } catch (error) { |
| + | throw new Error('Invalid URL format'); |
| + | } |
| + | } |
| + | |
| + | export function validateEmail(email) { |
| + | const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
| + | |
| + | if (!pattern.test(email)) { |
| + | throw new Error('Invalid email format'); |
| + | } |
| + | |
| + | // Basic length check |
| + | if (email.length > 255) { |
| + | throw new Error('Email address too long'); |
| + | } |
| + | |
| + | return true; |
| + | } |
| + | |
| + | export function sanitizeInput(input) { |
| + | if (typeof input !== 'string') return input; |
| + | |
| + | // Remove any HTML tags |
| + | return input.replace(/<[^>]*>/g, '').trim(); |
| + | } |
| \ No newline at end of file | |
wrangler.toml
+41
-0
| @@ | @@ -0,0 +1,41 @@ |
| + | name = "qrurl" |
| + | main = "src/index.js" |
| + | compatibility_date = "2024-12-01" |
| + | compatibility_flags = ["nodejs_compat"] |
| + | account_id = "b253e6fbfd2f7757cadd0386de5bde3f" |
| + | |
| + | # D1 Database |
| + | [[d1_databases]] |
| + | binding = "DB" |
| + | database_name = "qrurl-db" |
| + | database_id = "17eb6fdb-19da-4ed7-931c-a4cdef281f8c" |
| + | |
| + | # R2 Storage |
| + | [[r2_buckets]] |
| + | binding = "STORAGE" |
| + | bucket_name = "qrurl-storage" |
| + | |
| + | # KV Namespace for caching |
| + | [[kv_namespaces]] |
| + | binding = "CACHE" |
| + | id = "1cacb0f1b44b4324b62c1bc010ff15f5" |
| + | preview_id = "981af79732c84684b54fbbe10aa81f6e" |
| + | |
| + | # Environment Variables (set these in dashboard or .dev.vars) |
| + | [vars] |
| + | JWT_SECRET = "" |
| + | EMAIL_API_KEY = "" |
| + | FRONTEND_URL = "http://localhost:3000" |
| + | BACKEND_URL = "http://localhost:8787" |
| + | |
| + | # Development settings |
| + | [dev] |
| + | port = 8787 |
| + | local_protocol = "http" |
| + | |
| + | # Rate limiting |
| + | [[unsafe.bindings]] |
| + | name = "RATE_LIMITER" |
| + | type = "ratelimit" |
| + | namespace_id = "1" |
| + | simple = { limit = 10, period = 60 } |
| \ No newline at end of file | |