diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 79a6bed..e4fa3b8 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -14,7 +14,24 @@
"WebFetch(domain:github.com)",
"WebFetch(domain:www.npmjs.com)",
"WebFetch(domain:raw.githubusercontent.com)",
- "WebFetch(domain:api.github.com)"
+ "WebFetch(domain:api.github.com)",
+ "Bash(gh pr view:*)",
+ "Bash(gh pr diff:*)",
+ "Bash(gh pr checkout:*)",
+ "Bash(git commit:*)",
+ "Bash(git push:*)",
+ "Bash(gh run list:*)",
+ "Bash(gh workflow list:*)",
+ "Bash(grep:*)",
+ "Bash(gh run:*)",
+ "Bash(gh pr create:*)",
+ "Bash(gh pr:*)",
+ "Bash(./dist/flashforge-webui-win-x64.exe --no-printers)",
+ "Bash(python:*)",
+ "Bash(npx yao-pkg:*)",
+ "Bash(gh api:*)",
+ "Bash(ls:*)",
+ "Bash(git remote get-url:*)"
],
"deny": [],
"ask": []
diff --git a/.claude/skills/express-skill/SKILL.md b/.claude/skills/express-skill/SKILL.md
new file mode 100644
index 0000000..2ca7320
--- /dev/null
+++ b/.claude/skills/express-skill/SKILL.md
@@ -0,0 +1,248 @@
+---
+name: express
+description: Express.js 5.x web framework for Node.js - complete API reference, middleware patterns, routing, error handling, and production best practices. Use when building Express applications, creating REST APIs, implementing middleware, handling routes, managing errors, configuring security, optimizing performance, or migrating from Express 4 to 5. Triggers on Express, express.js, Node.js web server, REST API, middleware, routing, req/res objects, app.use, app.get, app.post, router, error handling, body-parser, static files, template engines.
+---
+
+# Express.js 5.x Development Skill
+
+Express is a minimal and flexible Node.js web application framework providing robust features for web and mobile applications. This skill covers Express 5.x (current stable, requires Node.js 18+).
+
+## Quick Reference
+
+### Creating an Express Application
+
+```javascript
+const express = require('express')
+const app = express()
+
+// Built-in middleware
+app.use(express.json()) // Parse JSON bodies
+app.use(express.urlencoded({ extended: true })) // Parse URL-encoded bodies
+app.use(express.static('public')) // Serve static files
+
+// Route handling
+app.get('/', (req, res) => {
+ res.send('Hello World')
+})
+
+app.listen(3000, () => {
+ console.log('Server running on port 3000')
+})
+```
+
+### Express 5.x Key Changes from v4
+
+Express 5 has breaking changes - review before migrating:
+
+- **Async error handling**: Rejected promises automatically call `next(err)`
+- **Wildcard routes**: Must be named: `/*splat` not `/*`
+- **Optional params**: Use braces: `/:file{.:ext}` not `/:file.:ext?`
+- **Removed methods**: `app.del()` → `app.delete()`, `res.sendfile()` → `res.sendFile()`
+- **req.body**: Returns `undefined` (not `{}`) when unparsed
+- **req.query**: No longer writable, uses "simple" parser by default
+
+See `references/guide/migration-v5.md` for complete migration guide.
+
+## Reference Documentation Structure
+
+```
+references/
+├── api/
+│ └── api-reference.md # Complete API: express(), app, req, res, router
+├── guide/
+│ ├── routing.md # Route methods, paths, parameters, handlers
+│ ├── middleware.md # Writing and using middleware
+│ ├── error-handling.md # Error catching and custom handlers
+│ └── migration-v5.md # Express 4 to 5 migration guide
+├── advanced/
+│ ├── security.md # Security best practices (Helmet, TLS, cookies)
+│ └── performance.md # Performance optimization and deployment
+└── getting-started/
+ └── quickstart.md # Installation, hello world, project setup
+```
+
+## Core Concepts
+
+### Application Object (`app`)
+
+The `app` object represents the Express application:
+
+```javascript
+const app = express()
+
+// Settings
+app.set('view engine', 'pug')
+app.set('trust proxy', true)
+app.enable('case sensitive routing')
+
+// Mounting middleware and routes
+app.use(middleware)
+app.use('/api', apiRouter)
+
+// HTTP methods
+app.get('/path', handler)
+app.post('/path', handler)
+app.put('/path', handler)
+app.delete('/path', handler)
+app.all('/path', handler) // All methods
+```
+
+### Request Object (`req`)
+
+Key properties and methods:
+
+| Property | Description |
+|----------|-------------|
+| `req.params` | Route parameters (e.g., `/users/:id` → `req.params.id`) |
+| `req.query` | Query string parameters |
+| `req.body` | Parsed request body (requires body-parsing middleware) |
+| `req.headers` | Request headers |
+| `req.method` | HTTP method |
+| `req.path` | Request path |
+| `req.ip` | Client IP address |
+| `req.cookies` | Cookies (requires cookie-parser) |
+
+### Response Object (`res`)
+
+Key methods:
+
+| Method | Description |
+|--------|-------------|
+| `res.send(body)` | Send response (auto content-type) |
+| `res.json(obj)` | Send JSON response |
+| `res.status(code)` | Set status code (chainable) |
+| `res.redirect([status,] path)` | Redirect request |
+| `res.render(view, locals)` | Render template |
+| `res.sendFile(path)` | Send file |
+| `res.download(path)` | Prompt file download |
+| `res.set(header, value)` | Set response header |
+| `res.cookie(name, value)` | Set cookie |
+
+### Router
+
+Create modular route handlers:
+
+```javascript
+const router = express.Router()
+
+router.use(authMiddleware) // Router-specific middleware
+
+router.get('/', listUsers)
+router.get('/:id', getUser)
+router.post('/', createUser)
+router.put('/:id', updateUser)
+router.delete('/:id', deleteUser)
+
+// Mount in app
+app.use('/users', router)
+```
+
+## Common Patterns
+
+### Middleware Chain
+
+```javascript
+// Logging → Auth → Route Handler → Error Handler
+app.use(morgan('combined'))
+app.use(authMiddleware)
+app.use('/api', apiRoutes)
+app.use(errorHandler) // Must be last, has 4 params: (err, req, res, next)
+```
+
+### Error Handling (Express 5)
+
+```javascript
+// Async errors are caught automatically in Express 5
+app.get('/user/:id', async (req, res) => {
+ const user = await User.findById(req.params.id) // Errors auto-forwarded
+ if (!user) {
+ const err = new Error('User not found')
+ err.status = 404
+ throw err
+ }
+ res.json(user)
+})
+
+// Error handler middleware (4 arguments required)
+app.use((err, req, res, next) => {
+ console.error(err.stack)
+ res.status(err.status || 500).json({
+ error: process.env.NODE_ENV === 'production'
+ ? 'Internal server error'
+ : err.message
+ })
+})
+```
+
+### RESTful API Structure
+
+```javascript
+const express = require('express')
+const app = express()
+
+app.use(express.json())
+
+// Routes
+const usersRouter = require('./routes/users')
+const postsRouter = require('./routes/posts')
+
+app.use('/api/users', usersRouter)
+app.use('/api/posts', postsRouter)
+
+// 404 handler
+app.use((req, res) => {
+ res.status(404).json({ error: 'Not found' })
+})
+
+// Error handler
+app.use((err, req, res, next) => {
+ res.status(err.status || 500).json({ error: err.message })
+})
+```
+
+### Static Files with Virtual Path
+
+```javascript
+// Serve ./public at /static
+app.use('/static', express.static('public', {
+ maxAge: '1d',
+ etag: true,
+ index: 'index.html'
+}))
+```
+
+## When to Read Reference Files
+
+| Task | Reference File |
+|------|----------------|
+| Look up specific API method | `references/api/api-reference.md` |
+| Implement routing patterns | `references/guide/routing.md` |
+| Create custom middleware | `references/guide/middleware.md` |
+| Handle errors properly | `references/guide/error-handling.md` |
+| Migrate from Express 4 | `references/guide/migration-v5.md` |
+| Security hardening | `references/advanced/security.md` |
+| Performance optimization | `references/advanced/performance.md` |
+| Set up new project | `references/getting-started/quickstart.md` |
+
+## Production Checklist
+
+1. **Security**: Use Helmet, set secure cookies, validate input
+2. **Performance**: Enable gzip, use clustering, cache responses
+3. **Environment**: Set `NODE_ENV=production`
+4. **Error handling**: Never expose stack traces in production
+5. **Logging**: Use async logger (Pino), not console.log
+6. **Process management**: Use systemd or PM2
+7. **Reverse proxy**: Run behind Nginx/HAProxy for TLS and load balancing
+
+## Key Dependencies
+
+| Package | Purpose |
+|---------|---------|
+| `helmet` | Security headers |
+| `cors` | CORS handling |
+| `morgan` | HTTP request logging |
+| `compression` | Gzip compression |
+| `cookie-parser` | Cookie parsing |
+| `express-session` | Session management |
+| `express-validator` | Input validation |
+| `multer` | File uploads |
diff --git a/.claude/skills/express-skill/references/advanced/performance.md b/.claude/skills/express-skill/references/advanced/performance.md
new file mode 100644
index 0000000..046c53c
--- /dev/null
+++ b/.claude/skills/express-skill/references/advanced/performance.md
@@ -0,0 +1,533 @@
+# Express Performance Best Practices
+
+Performance and reliability best practices for Express applications in production.
+
+## Things to Do in Your Code
+
+### Use Gzip Compression
+
+Compress responses to reduce payload size:
+
+```bash
+npm install compression
+```
+
+```javascript
+const compression = require('compression')
+const express = require('express')
+const app = express()
+
+app.use(compression())
+```
+
+**Production tip**: For high-traffic sites, implement compression at the reverse proxy (Nginx) instead:
+
+```nginx
+# nginx.conf
+gzip on;
+gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
+gzip_min_length 1000;
+```
+
+### Don't Use Synchronous Functions
+
+Synchronous functions block the event loop:
+
+```javascript
+// AVOID in production
+const data = fs.readFileSync('/file.json')
+
+// USE async versions
+const data = await fs.promises.readFile('/file.json')
+// or
+fs.readFile('/file.json', (err, data) => { ... })
+```
+
+Detect sync calls with `--trace-sync-io`:
+
+```bash
+node --trace-sync-io app.js
+```
+
+### Do Logging Correctly
+
+`console.log()` and `console.error()` are **synchronous** when writing to terminal/file.
+
+#### For Debugging
+
+Use the [debug](https://www.npmjs.com/package/debug) module:
+
+```bash
+npm install debug
+```
+
+```javascript
+const debug = require('debug')('app:server')
+debug('Server starting on port %d', port)
+```
+
+```bash
+DEBUG=app:* node app.js
+```
+
+#### For Application Logging
+
+Use [Pino](https://www.npmjs.com/package/pino) - fastest Node.js logger:
+
+```bash
+npm install pino pino-http
+```
+
+```javascript
+const pino = require('pino')
+const pinoHttp = require('pino-http')
+
+const logger = pino({
+ level: process.env.LOG_LEVEL || 'info',
+ transport: process.env.NODE_ENV !== 'production'
+ ? { target: 'pino-pretty' }
+ : undefined
+})
+
+app.use(pinoHttp({ logger }))
+```
+
+### Handle Exceptions Properly
+
+#### Use try-catch
+
+For synchronous code:
+
+```javascript
+app.get('/search', (req, res) => {
+ setImmediate(() => {
+ const jsonStr = req.query.params
+ try {
+ const jsonObj = JSON.parse(jsonStr)
+ res.send('Success')
+ } catch (e) {
+ res.status(400).send('Invalid JSON string')
+ }
+ })
+})
+```
+
+#### Use Promises (Express 5)
+
+Express 5 automatically catches promise rejections:
+
+```javascript
+app.get('/', async (req, res) => {
+ const data = await fetchData() // Errors auto-forwarded to error handler
+ res.send(data)
+})
+
+app.use((err, req, res, next) => {
+ res.status(err.status ?? 500).send({ error: err.message })
+})
+```
+
+#### Async Middleware (Express 5)
+
+```javascript
+app.use(async (req, res, next) => {
+ req.locals.user = await getUser(req)
+ next() // Called if promise doesn't throw
+})
+```
+
+#### What NOT to Do
+
+**Never** use `uncaughtException`:
+
+```javascript
+// BAD - Don't do this
+process.on('uncaughtException', (err) => {
+ console.log('Caught exception:', err)
+})
+```
+
+This keeps the app running in an unreliable state. Let it crash and use a process manager to restart.
+
+**Never** use the deprecated `domain` module.
+
+## Things to Do in Your Environment
+
+### Set NODE_ENV to "production"
+
+This alone can improve performance 3x:
+
+```bash
+NODE_ENV=production node app.js
+```
+
+In production, Express:
+- Caches view templates
+- Caches CSS from CSS extensions
+- Generates less verbose error messages
+
+With systemd:
+
+```ini
+# /etc/systemd/system/myapp.service
+[Service]
+Environment=NODE_ENV=production
+```
+
+### Ensure App Automatically Restarts
+
+#### Using systemd (Recommended)
+
+Create `/etc/systemd/system/myapp.service`:
+
+```ini
+[Unit]
+Description=My Express App
+After=network.target
+
+[Service]
+Type=simple
+User=www-data
+WorkingDirectory=/var/www/myapp
+ExecStart=/usr/bin/node /var/www/myapp/index.js
+Restart=always
+RestartSec=10
+
+Environment=NODE_ENV=production
+Environment=PORT=3000
+
+# Allow many incoming connections
+LimitNOFILE=infinity
+
+# Standard output/error
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=myapp
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Enable and start:
+
+```bash
+sudo systemctl enable myapp
+sudo systemctl start myapp
+```
+
+#### Using PM2
+
+```bash
+npm install -g pm2
+pm2 start app.js --name myapp -i max
+pm2 save
+pm2 startup
+```
+
+### Run Your App in a Cluster
+
+Use all CPU cores with Node's cluster module:
+
+```javascript
+const cluster = require('cluster')
+const numCPUs = require('os').cpus().length
+
+if (cluster.isPrimary) {
+ console.log(`Primary ${process.pid} is running`)
+
+ // Fork workers
+ for (let i = 0; i < numCPUs; i++) {
+ cluster.fork()
+ }
+
+ cluster.on('exit', (worker, code, signal) => {
+ console.log(`Worker ${worker.process.pid} died, restarting...`)
+ cluster.fork()
+ })
+} else {
+ // Workers share TCP connection
+ const app = require('./app')
+ app.listen(3000)
+ console.log(`Worker ${process.pid} started`)
+}
+```
+
+Or use PM2's cluster mode:
+
+```bash
+pm2 start app.js -i max # Auto-detect CPUs
+pm2 start app.js -i 4 # 4 workers
+```
+
+**Important**: Clustered apps cannot share memory. Use Redis for sessions and shared state.
+
+### Cache Request Results
+
+#### Application-Level Caching
+
+```javascript
+const NodeCache = require('node-cache')
+const cache = new NodeCache({ stdTTL: 600 }) // 10 min default
+
+app.get('/data/:id', async (req, res) => {
+ const cacheKey = `data_${req.params.id}`
+
+ let data = cache.get(cacheKey)
+ if (!data) {
+ data = await fetchDataFromDB(req.params.id)
+ cache.set(cacheKey, data)
+ }
+
+ res.json(data)
+})
+```
+
+#### Redis Caching
+
+```javascript
+const redis = require('redis')
+const client = redis.createClient()
+
+app.get('/data/:id', async (req, res) => {
+ const cacheKey = `data:${req.params.id}`
+
+ const cached = await client.get(cacheKey)
+ if (cached) {
+ return res.json(JSON.parse(cached))
+ }
+
+ const data = await fetchDataFromDB(req.params.id)
+ await client.setEx(cacheKey, 3600, JSON.stringify(data)) // 1 hour
+ res.json(data)
+})
+```
+
+#### Reverse Proxy Caching (Nginx)
+
+```nginx
+proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
+
+server {
+ location /api/ {
+ proxy_cache my_cache;
+ proxy_cache_valid 200 10m;
+ proxy_cache_valid 404 1m;
+ proxy_pass http://localhost:3000;
+ }
+}
+```
+
+### Use a Load Balancer
+
+Distribute traffic across multiple instances:
+
+#### Nginx Load Balancer
+
+```nginx
+upstream myapp {
+ least_conn; # or ip_hash for sticky sessions
+ server 127.0.0.1:3001;
+ server 127.0.0.1:3002;
+ server 127.0.0.1:3003;
+ server 127.0.0.1:3004;
+}
+
+server {
+ listen 80;
+
+ location / {
+ proxy_pass http://myapp;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Host $host;
+ proxy_cache_bypass $http_upgrade;
+ }
+}
+```
+
+### Use a Reverse Proxy
+
+Run Express behind Nginx or HAProxy for:
+- TLS termination
+- Gzip compression
+- Static file serving
+- Load balancing
+- Caching
+- Rate limiting
+
+#### Nginx Configuration
+
+```nginx
+server {
+ listen 443 ssl http2;
+ server_name example.com;
+
+ ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
+
+ # Gzip
+ gzip on;
+ gzip_types text/plain text/css application/json application/javascript;
+
+ # Static files
+ location /static/ {
+ alias /var/www/myapp/public/;
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # API
+ location / {
+ proxy_pass http://localhost:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+Configure Express to trust the proxy:
+
+```javascript
+app.set('trust proxy', 1) // Trust first proxy
+```
+
+## Health Checks and Graceful Shutdown
+
+### Health Check Endpoint
+
+```javascript
+app.get('/health', (req, res) => {
+ res.status(200).json({ status: 'healthy' })
+})
+
+// Detailed health check
+app.get('/health/detailed', async (req, res) => {
+ const health = {
+ uptime: process.uptime(),
+ message: 'OK',
+ timestamp: Date.now(),
+ checks: {}
+ }
+
+ try {
+ await db.query('SELECT 1')
+ health.checks.database = 'healthy'
+ } catch (e) {
+ health.checks.database = 'unhealthy'
+ health.message = 'Degraded'
+ }
+
+ try {
+ await redis.ping()
+ health.checks.redis = 'healthy'
+ } catch (e) {
+ health.checks.redis = 'unhealthy'
+ health.message = 'Degraded'
+ }
+
+ const status = health.message === 'OK' ? 200 : 503
+ res.status(status).json(health)
+})
+```
+
+### Graceful Shutdown
+
+```javascript
+const server = app.listen(3000)
+
+process.on('SIGTERM', gracefulShutdown)
+process.on('SIGINT', gracefulShutdown)
+
+function gracefulShutdown() {
+ console.log('Received shutdown signal, closing server...')
+
+ server.close(() => {
+ console.log('HTTP server closed')
+
+ // Close database connections
+ db.end(() => {
+ console.log('Database connections closed')
+ process.exit(0)
+ })
+ })
+
+ // Force close after timeout
+ setTimeout(() => {
+ console.error('Could not close connections in time, forcing shutdown')
+ process.exit(1)
+ }, 30000)
+}
+```
+
+## Performance Monitoring
+
+### Built-in Metrics
+
+```javascript
+app.get('/metrics', (req, res) => {
+ res.json({
+ memory: process.memoryUsage(),
+ uptime: process.uptime(),
+ cpuUsage: process.cpuUsage()
+ })
+})
+```
+
+### Response Time Header
+
+```javascript
+const onHeaders = require('on-headers')
+
+app.use((req, res, next) => {
+ const start = Date.now()
+
+ onHeaders(res, () => {
+ const duration = Date.now() - start
+ res.setHeader('X-Response-Time', `${duration}ms`)
+ })
+
+ next()
+})
+```
+
+### APM Tools
+
+Consider using Application Performance Monitoring:
+- Datadog
+- New Relic
+- Dynatrace
+- Elastic APM
+
+## Performance Checklist
+
+### Code
+
+- [ ] Use async functions, avoid sync operations
+- [ ] Use Pino for logging (not console.log)
+- [ ] Handle errors properly with try-catch and promises
+- [ ] Implement caching where appropriate
+- [ ] Compress responses
+
+### Environment
+
+- [ ] Set `NODE_ENV=production`
+- [ ] Use a process manager (systemd/PM2)
+- [ ] Run in cluster mode (multiple workers)
+- [ ] Use a reverse proxy (Nginx)
+- [ ] Enable HTTP/2
+- [ ] Use TLS 1.3
+- [ ] Implement health checks
+- [ ] Add graceful shutdown handling
+
+### Infrastructure
+
+- [ ] Use a CDN for static assets
+- [ ] Implement database connection pooling
+- [ ] Use Redis for sessions and caching
+- [ ] Set up load balancing for multiple servers
+- [ ] Configure proper timeouts
+- [ ] Monitor application metrics
diff --git a/.claude/skills/express-skill/references/advanced/security.md b/.claude/skills/express-skill/references/advanced/security.md
new file mode 100644
index 0000000..7526969
--- /dev/null
+++ b/.claude/skills/express-skill/references/advanced/security.md
@@ -0,0 +1,461 @@
+# Express Security Best Practices
+
+Security best practices for Express applications in production.
+
+## Overview
+
+Production environments have vastly different requirements from development:
+- Verbose error logging becomes a security concern
+- Scalability, reliability, and performance become critical
+- Security vulnerabilities can be exploited
+
+## Don't Use Deprecated or Vulnerable Versions
+
+- Express 2.x and 3.x are no longer maintained
+- Check the [Security Updates page](https://expressjs.com/en/advanced/security-updates.html)
+- Update to the latest stable release
+
+```bash
+npm install express@latest
+npm audit
+```
+
+## Use TLS (HTTPS)
+
+If your app deals with sensitive data, use Transport Layer Security:
+
+- Encrypts data before transmission
+- Prevents packet sniffing and man-in-the-middle attacks
+- Use Nginx to handle TLS termination
+
+**Resources:**
+- [Let's Encrypt](https://letsencrypt.org/) - Free TLS certificates
+- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)
+
+## Do Not Trust User Input
+
+One of the most critical security requirements is proper input validation.
+
+### Prevent Open Redirects
+
+Never redirect to user-supplied URLs without validation:
+
+```javascript
+// VULNERABLE
+app.get('/redirect', (req, res) => {
+ res.redirect(req.query.url) // Attacker can redirect to phishing site
+})
+
+// SECURE
+app.get('/redirect', (req, res) => {
+ try {
+ const url = new URL(req.query.url)
+ if (url.host !== 'example.com') {
+ return res.status(400).send(`Unsupported redirect to host: ${url.host}`)
+ }
+ res.redirect(req.query.url)
+ } catch (e) {
+ res.status(400).send(`Invalid url: ${req.query.url}`)
+ }
+})
+```
+
+### Input Validation
+
+Use validation libraries:
+
+```javascript
+const { body, validationResult } = require('express-validator')
+
+app.post('/user',
+ body('email').isEmail().normalizeEmail(),
+ body('password').isLength({ min: 8 }),
+ body('name').trim().escape(),
+ (req, res) => {
+ const errors = validationResult(req)
+ if (!errors.isEmpty()) {
+ return res.status(400).json({ errors: errors.array() })
+ }
+ // Process validated input
+ }
+)
+```
+
+## Use Helmet
+
+[Helmet](https://helmetjs.github.io/) sets security-related HTTP headers:
+
+```bash
+npm install helmet
+```
+
+```javascript
+const helmet = require('helmet')
+app.use(helmet())
+```
+
+### Headers Set by Helmet (Defaults)
+
+| Header | Purpose |
+|--------|---------|
+| `Content-Security-Policy` | Mitigates XSS and data injection attacks |
+| `Cross-Origin-Opener-Policy` | Process-isolates your page |
+| `Cross-Origin-Resource-Policy` | Blocks cross-origin resource loading |
+| `Origin-Agent-Cluster` | Origin-based process isolation |
+| `Referrer-Policy` | Controls Referer header |
+| `Strict-Transport-Security` | Enforces HTTPS |
+| `X-Content-Type-Options` | Prevents MIME sniffing |
+| `X-DNS-Prefetch-Control` | Controls DNS prefetching |
+| `X-Download-Options` | Forces downloads to be saved (IE only) |
+| `X-Frame-Options` | Mitigates clickjacking |
+| `X-Permitted-Cross-Domain-Policies` | Controls Adobe cross-domain behavior |
+| `X-XSS-Protection` | Disabled (can make things worse) |
+
+### Custom Helmet Configuration
+
+```javascript
+app.use(helmet({
+ contentSecurityPolicy: {
+ directives: {
+ defaultSrc: ["'self'"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "cdn.example.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ imgSrc: ["'self'", "data:", "*.cloudinary.com"],
+ },
+ },
+ crossOriginEmbedderPolicy: false,
+}))
+```
+
+## Reduce Fingerprinting
+
+Disable the `X-Powered-By` header:
+
+```javascript
+app.disable('x-powered-by')
+```
+
+Customize error responses to avoid revealing Express:
+
+```javascript
+// Custom 404
+app.use((req, res, next) => {
+ res.status(404).send("Sorry can't find that!")
+})
+
+// Custom error handler
+app.use((err, req, res, next) => {
+ console.error(err.stack)
+ res.status(500).send('Something broke!')
+})
+```
+
+## Use Cookies Securely
+
+### Don't Use Default Session Cookie Name
+
+```javascript
+const session = require('express-session')
+
+app.use(session({
+ secret: process.env.SESSION_SECRET,
+ name: 'sessionId', // Change from default 'connect.sid'
+ resave: false,
+ saveUninitialized: false,
+}))
+```
+
+### Set Cookie Security Options
+
+```javascript
+const session = require('cookie-session')
+
+app.use(session({
+ name: 'session',
+ keys: [process.env.COOKIE_KEY1, process.env.COOKIE_KEY2],
+ cookie: {
+ secure: true, // HTTPS only
+ httpOnly: true, // No client JS access
+ domain: 'example.com',
+ path: '/',
+ sameSite: 'strict', // CSRF protection
+ maxAge: 24 * 60 * 60 * 1000 // 24 hours
+ }
+}))
+```
+
+### Cookie Options Explained
+
+| Option | Description |
+|--------|-------------|
+| `secure` | Only send over HTTPS |
+| `httpOnly` | Prevents client JavaScript access (XSS protection) |
+| `domain` | Cookie domain scope |
+| `path` | Cookie path scope |
+| `sameSite` | CSRF protection: `'strict'`, `'lax'`, or `'none'` |
+| `maxAge` | Expiration time in milliseconds |
+| `expires` | Expiration date |
+
+### Session Storage Options
+
+**express-session**: Stores session data on server, only session ID in cookie
+- Use a production session store (Redis, MongoDB, etc.)
+- Default in-memory store is not for production
+
+**cookie-session**: Stores entire session in cookie
+- Good for small, non-sensitive session data
+- Keep under 4093 bytes
+
+```javascript
+// express-session with Redis
+const session = require('express-session')
+const RedisStore = require('connect-redis').default
+const { createClient } = require('redis')
+
+const redisClient = createClient()
+redisClient.connect()
+
+app.use(session({
+ store: new RedisStore({ client: redisClient }),
+ secret: process.env.SESSION_SECRET,
+ resave: false,
+ saveUninitialized: false,
+ cookie: { secure: true, httpOnly: true, sameSite: 'strict' }
+}))
+```
+
+## Prevent Brute-Force Attacks
+
+Use rate limiting for authentication endpoints:
+
+```bash
+npm install rate-limiter-flexible
+```
+
+```javascript
+const { RateLimiterMemory } = require('rate-limiter-flexible')
+
+const rateLimiter = new RateLimiterMemory({
+ points: 10, // 10 attempts
+ duration: 60, // per 60 seconds
+})
+
+app.post('/login', async (req, res, next) => {
+ try {
+ await rateLimiter.consume(req.ip)
+ // Proceed with login
+ } catch (rejRes) {
+ res.status(429).send('Too Many Requests')
+ }
+})
+```
+
+### More Sophisticated Rate Limiting
+
+```javascript
+const { RateLimiterRedis } = require('rate-limiter-flexible')
+
+// Block by IP + username combination
+const limiterConsecutiveFailsByUsernameAndIP = new RateLimiterRedis({
+ storeClient: redisClient,
+ keyPrefix: 'login_fail_consecutive_username_and_ip',
+ points: 10,
+ duration: 60 * 60 * 24, // 24 hours
+ blockDuration: 60 * 60, // Block for 1 hour
+})
+
+// Block by IP only for distributed attacks
+const limiterSlowBruteByIP = new RateLimiterRedis({
+ storeClient: redisClient,
+ keyPrefix: 'login_fail_ip_per_day',
+ points: 100,
+ duration: 60 * 60 * 24, // 24 hours
+ blockDuration: 60 * 60 * 24, // Block for 24 hours
+})
+```
+
+## Ensure Dependencies Are Secure
+
+### npm audit
+
+```bash
+npm audit
+npm audit fix
+```
+
+### Snyk
+
+```bash
+npm install -g snyk
+snyk test
+snyk monitor # Continuous monitoring
+```
+
+### Keep Dependencies Updated
+
+```bash
+npm outdated
+npm update
+```
+
+## Prevent SQL Injection
+
+Use parameterized queries or ORMs:
+
+```javascript
+// VULNERABLE
+const query = `SELECT * FROM users WHERE id = ${req.params.id}`
+
+// SECURE - Parameterized query
+const query = 'SELECT * FROM users WHERE id = ?'
+db.query(query, [req.params.id])
+
+// SECURE - Using an ORM (Sequelize)
+const user = await User.findByPk(req.params.id)
+```
+
+## Prevent Cross-Site Scripting (XSS)
+
+### Sanitize Output
+
+Use template engines that escape output by default (Pug, EJS, Handlebars).
+
+```javascript
+// Manual escaping if needed
+const escapeHtml = (str) => {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+}
+```
+
+### Content Security Policy
+
+Configure CSP with Helmet:
+
+```javascript
+app.use(helmet.contentSecurityPolicy({
+ directives: {
+ defaultSrc: ["'self'"],
+ scriptSrc: ["'self'"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ imgSrc: ["'self'", "data:"],
+ connectSrc: ["'self'"],
+ fontSrc: ["'self'"],
+ objectSrc: ["'none'"],
+ mediaSrc: ["'self'"],
+ frameSrc: ["'none'"],
+ },
+}))
+```
+
+## Prevent CSRF
+
+Use CSRF tokens:
+
+```bash
+npm install csurf
+```
+
+```javascript
+const csrf = require('csurf')
+
+// After cookie-parser and session middleware
+app.use(csrf({ cookie: true }))
+
+// Pass token to views
+app.use((req, res, next) => {
+ res.locals.csrfToken = req.csrfToken()
+ next()
+})
+
+// In form template
+//
+```
+
+Or use `sameSite` cookies (simpler):
+
+```javascript
+app.use(session({
+ cookie: {
+ sameSite: 'strict' // Or 'lax' for GET requests from external sites
+ }
+}))
+```
+
+## Additional Security Measures
+
+### Disable Directory Listing
+
+```javascript
+// express.static doesn't list directories by default
+// But ensure no other middleware does
+```
+
+### Limit Request Body Size
+
+```javascript
+app.use(express.json({ limit: '10kb' }))
+app.use(express.urlencoded({ limit: '10kb', extended: true }))
+```
+
+### Set Appropriate Timeouts
+
+```javascript
+const server = app.listen(3000)
+server.setTimeout(30000) // 30 seconds
+server.keepAliveTimeout = 65000 // Slightly higher than ALB timeout
+```
+
+### Use Regular Expression Safely
+
+Avoid ReDoS attacks:
+
+```bash
+npm install safe-regex
+```
+
+```javascript
+const safeRegex = require('safe-regex')
+
+if (!safeRegex(userProvidedRegex)) {
+ throw new Error('Invalid regex pattern')
+}
+```
+
+## Security Checklist
+
+### Essential
+
+- [ ] Use HTTPS (TLS)
+- [ ] Use Helmet
+- [ ] Disable `x-powered-by`
+- [ ] Validate and sanitize all user input
+- [ ] Use parameterized queries
+- [ ] Set secure cookie options
+- [ ] Implement rate limiting
+- [ ] Keep dependencies updated
+- [ ] Run `npm audit` regularly
+
+### Recommended
+
+- [ ] Use Content Security Policy
+- [ ] Implement CSRF protection
+- [ ] Use HTTP Strict Transport Security (HSTS)
+- [ ] Limit request body size
+- [ ] Set appropriate timeouts
+- [ ] Log security events
+- [ ] Use secure session storage (Redis, etc.)
+- [ ] Implement account lockout after failed attempts
+
+### Production Environment
+
+- [ ] Set `NODE_ENV=production`
+- [ ] Don't expose error details to clients
+- [ ] Use a reverse proxy (Nginx)
+- [ ] Enable request logging
+- [ ] Monitor for security events
+- [ ] Have an incident response plan
diff --git a/.claude/skills/express-skill/references/api/api-reference.md b/.claude/skills/express-skill/references/api/api-reference.md
new file mode 100644
index 0000000..6a30e59
--- /dev/null
+++ b/.claude/skills/express-skill/references/api/api-reference.md
@@ -0,0 +1,1093 @@
+# Express 5.x API Reference
+
+Complete API documentation for Express.js 5.x. Express 5.0 requires Node.js 18 or higher.
+
+## express()
+
+Creates an Express application - the top-level function exported by the express module.
+
+```javascript
+const express = require('express')
+const app = express()
+```
+
+### Built-in Middleware
+
+#### express.json([options])
+
+Parses incoming requests with JSON payloads. Returns middleware that only parses JSON where `Content-Type` matches the `type` option.
+
+```javascript
+app.use(express.json())
+app.use(express.json({ limit: '10mb', strict: true }))
+```
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `inflate` | Boolean | `true` | Handle deflated (compressed) bodies |
+| `limit` | Mixed | `"100kb"` | Max request body size |
+| `reviver` | Function | `null` | Passed to `JSON.parse` as reviver |
+| `strict` | Boolean | `true` | Only accept arrays and objects |
+| `type` | Mixed | `"application/json"` | Media type to parse |
+| `verify` | Function | `undefined` | Function to verify raw body |
+
+#### express.urlencoded([options])
+
+Parses incoming requests with URL-encoded payloads.
+
+```javascript
+app.use(express.urlencoded({ extended: true }))
+```
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `extended` | Boolean | `false` | Use `qs` library (true) or `querystring` (false) |
+| `inflate` | Boolean | `true` | Handle deflated bodies |
+| `limit` | Mixed | `"100kb"` | Max request body size |
+| `parameterLimit` | Number | `1000` | Max number of parameters |
+| `type` | Mixed | `"application/x-www-form-urlencoded"` | Media type to parse |
+| `depth` | Number | `32` | Max depth for `qs` library parsing |
+
+#### express.static(root, [options])
+
+Serves static files from the given root directory.
+
+```javascript
+app.use(express.static('public'))
+app.use('/static', express.static('files', { maxAge: '1d' }))
+```
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `dotfiles` | String | `"ignore"` | How to treat dotfiles: "allow", "deny", "ignore" |
+| `etag` | Boolean | `true` | Enable ETag generation |
+| `extensions` | Mixed | `false` | File extension fallbacks, e.g., `['html', 'htm']` |
+| `fallthrough` | Boolean | `true` | Let client errors fall-through |
+| `immutable` | Boolean | `false` | Enable immutable directive in Cache-Control |
+| `index` | Mixed | `"index.html"` | Directory index file |
+| `lastModified` | Boolean | `true` | Set Last-Modified header |
+| `maxAge` | Number | `0` | Max-age for Cache-Control in ms |
+| `redirect` | Boolean | `true` | Redirect to trailing "/" for directories |
+
+**Express 5 Change**: `dotfiles` now defaults to `"ignore"` (was served by default in v4).
+
+#### express.Router([options])
+
+Creates a new router object.
+
+```javascript
+const router = express.Router()
+const router = express.Router({ caseSensitive: true, mergeParams: true })
+```
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `caseSensitive` | Boolean | `false` | Enable case sensitivity |
+| `mergeParams` | Boolean | `false` | Preserve parent's req.params |
+| `strict` | Boolean | `false` | Enable strict routing ("/foo" ≠ "/foo/") |
+
+#### express.raw([options])
+
+Parses incoming request payloads into a Buffer.
+
+```javascript
+app.use(express.raw({ type: 'application/octet-stream' }))
+```
+
+#### express.text([options])
+
+Parses incoming request payloads into a string.
+
+```javascript
+app.use(express.text({ type: 'text/plain' }))
+```
+
+---
+
+## Application (app)
+
+The app object represents the Express application.
+
+### Properties
+
+#### app.locals
+
+Local variables available in templates rendered with `res.render()`. Persists throughout app lifetime.
+
+```javascript
+app.locals.title = 'My App'
+app.locals.email = 'admin@example.com'
+```
+
+#### app.mountpath
+
+Path patterns on which a sub-app was mounted.
+
+```javascript
+const admin = express()
+admin.get('/', (req, res) => {
+ console.log(admin.mountpath) // '/admin'
+})
+app.use('/admin', admin)
+```
+
+#### app.router
+
+The application's built-in router instance.
+
+```javascript
+const router = app.router
+router.get('/', handler)
+```
+
+### Events
+
+#### app.on('mount', callback)
+
+Fired when a sub-app is mounted on a parent app.
+
+```javascript
+const admin = express()
+admin.on('mount', (parent) => {
+ console.log('Admin mounted on parent')
+})
+app.use('/admin', admin)
+```
+
+### Methods
+
+#### app.all(path, callback [, callback ...])
+
+Matches all HTTP methods for a path.
+
+```javascript
+app.all('/secret', (req, res, next) => {
+ console.log('Accessing the secret section...')
+ next()
+})
+```
+
+#### app.delete(path, callback [, callback ...])
+
+Routes HTTP DELETE requests.
+
+```javascript
+app.delete('/user/:id', (req, res) => {
+ res.send(`DELETE user ${req.params.id}`)
+})
+```
+
+#### app.disable(name) / app.enable(name)
+
+Sets boolean settings to false/true.
+
+```javascript
+app.disable('x-powered-by')
+app.enable('trust proxy')
+```
+
+#### app.disabled(name) / app.enabled(name)
+
+Returns true if setting is disabled/enabled.
+
+```javascript
+app.disabled('trust proxy') // true
+app.enable('trust proxy')
+app.enabled('trust proxy') // true
+```
+
+#### app.engine(ext, callback)
+
+Registers a template engine.
+
+```javascript
+app.engine('html', require('ejs').renderFile)
+app.engine('pug', require('pug').__express)
+```
+
+#### app.get(name)
+
+Returns the value of an app setting.
+
+```javascript
+app.set('title', 'My Site')
+app.get('title') // "My Site"
+```
+
+#### app.get(path, callback [, callback ...])
+
+Routes HTTP GET requests.
+
+```javascript
+app.get('/', (req, res) => {
+ res.send('GET request to homepage')
+})
+```
+
+#### app.listen([port[, host[, backlog]]][, callback])
+
+Binds and listens for connections.
+
+```javascript
+app.listen(3000)
+app.listen(3000, () => console.log('Server running'))
+app.listen(3000, '0.0.0.0', () => console.log('Listening on all interfaces'))
+```
+
+**Express 5 Change**: Errors are now passed to callback instead of thrown.
+
+```javascript
+app.listen(3000, (error) => {
+ if (error) throw error // e.g., EADDRINUSE
+ console.log('Listening on port 3000')
+})
+```
+
+#### app.METHOD(path, callback [, callback ...])
+
+Routes HTTP requests where METHOD is the HTTP method (get, post, put, delete, etc.).
+
+Supported methods: `checkout`, `copy`, `delete`, `get`, `head`, `lock`, `merge`, `mkactivity`, `mkcol`, `move`, `m-search`, `notify`, `options`, `patch`, `post`, `purge`, `put`, `report`, `search`, `subscribe`, `trace`, `unlock`, `unsubscribe`
+
+#### app.param(name, callback)
+
+Add callback triggers to route parameters.
+
+```javascript
+app.param('user', (req, res, next, id) => {
+ User.find(id, (err, user) => {
+ if (err) return next(err)
+ if (!user) return next(new Error('User not found'))
+ req.user = user
+ next()
+ })
+})
+
+app.get('/user/:user', (req, res) => {
+ res.send(req.user)
+})
+```
+
+#### app.path()
+
+Returns the canonical path of the app.
+
+```javascript
+const blog = express()
+app.use('/blog', blog)
+blog.path() // '/blog'
+```
+
+#### app.post(path, callback [, callback ...])
+
+Routes HTTP POST requests.
+
+```javascript
+app.post('/user', (req, res) => {
+ res.send('POST request to /user')
+})
+```
+
+#### app.put(path, callback [, callback ...])
+
+Routes HTTP PUT requests.
+
+```javascript
+app.put('/user/:id', (req, res) => {
+ res.send(`PUT user ${req.params.id}`)
+})
+```
+
+#### app.render(view, [locals], callback)
+
+Returns rendered HTML of a view via callback.
+
+```javascript
+app.render('email', { name: 'Tobi' }, (err, html) => {
+ if (err) return console.error(err)
+ // html contains rendered template
+})
+```
+
+#### app.route(path)
+
+Returns a single route for chaining HTTP method handlers.
+
+```javascript
+app.route('/book')
+ .get((req, res) => res.send('Get a book'))
+ .post((req, res) => res.send('Add a book'))
+ .put((req, res) => res.send('Update the book'))
+ .delete((req, res) => res.send('Delete the book'))
+```
+
+#### app.set(name, value)
+
+Assigns setting name to value.
+
+```javascript
+app.set('title', 'My Site')
+app.set('views', './views')
+app.set('view engine', 'pug')
+```
+
+### Application Settings
+
+| Setting | Type | Default | Description |
+|---------|------|---------|-------------|
+| `case sensitive routing` | Boolean | `undefined` | "/Foo" and "/foo" are different |
+| `env` | String | `NODE_ENV` or "development" | Environment mode |
+| `etag` | Varied | `"weak"` | ETag response header |
+| `jsonp callback name` | String | `"callback"` | JSONP callback name |
+| `json escape` | Boolean | `undefined` | Escape `<`, `>`, `&` in JSON |
+| `json replacer` | Varied | `undefined` | JSON.stringify replacer |
+| `json spaces` | Varied | `undefined` | JSON.stringify spaces |
+| `query parser` | Varied | `"simple"` | Query string parser |
+| `strict routing` | Boolean | `undefined` | "/foo" and "/foo/" are different |
+| `subdomain offset` | Number | `2` | Subdomain parts to remove |
+| `trust proxy` | Varied | `false` | Trust X-Forwarded-* headers |
+| `views` | String/Array | `./views` | View directories |
+| `view cache` | Boolean | `true` in production | Cache view templates |
+| `view engine` | String | `undefined` | Default template engine |
+| `x-powered-by` | Boolean | `true` | Enable X-Powered-By header |
+
+##### trust proxy Options
+
+| Type | Value |
+|------|-------|
+| Boolean | `true`: trust all proxies; `false`: trust none |
+| String | IP address or subnet to trust (e.g., `'loopback'`, `'10.0.0.0/8'`) |
+| Number | Trust nth hop from front-facing proxy |
+| Function | Custom trust function `(ip) => boolean` |
+
+#### app.use([path,] callback [, callback...])
+
+Mounts middleware at the specified path.
+
+```javascript
+// All requests
+app.use((req, res, next) => {
+ console.log('Time:', Date.now())
+ next()
+})
+
+// Specific path
+app.use('/api', apiRouter)
+
+// Multiple middleware
+app.use('/user/:id', authenticate, loadUser)
+```
+
+---
+
+## Request (req)
+
+The req object represents the HTTP request.
+
+### Properties
+
+#### req.app
+
+Reference to the Express application.
+
+```javascript
+req.app.get('views')
+```
+
+#### req.baseUrl
+
+The URL path on which a router was mounted.
+
+```javascript
+// Mounted at /greet
+router.get('/jp', (req, res) => {
+ console.log(req.baseUrl) // '/greet'
+})
+```
+
+#### req.body
+
+Contains parsed request body. Requires body-parsing middleware.
+
+```javascript
+// With express.json()
+app.post('/user', (req, res) => {
+ console.log(req.body.name)
+})
+```
+
+**Express 5 Change**: Returns `undefined` when body not parsed (was `{}` in v4).
+
+#### req.cookies
+
+Contains cookies sent by the request. Requires cookie-parser middleware.
+
+```javascript
+// Cookie: name=tj
+req.cookies.name // "tj"
+```
+
+#### req.fresh / req.stale
+
+Indicates if response is still "fresh" in client's cache.
+
+```javascript
+if (req.fresh) {
+ res.status(304).end()
+}
+```
+
+#### req.host
+
+Host derived from Host header (includes port in Express 5).
+
+```javascript
+// Host: "example.com:3000"
+req.host // "example.com:3000"
+```
+
+#### req.hostname
+
+Hostname derived from Host header.
+
+```javascript
+// Host: "example.com:3000"
+req.hostname // "example.com"
+```
+
+#### req.ip
+
+Remote IP address of the request.
+
+```javascript
+req.ip // "127.0.0.1"
+```
+
+#### req.ips
+
+Array of IP addresses from X-Forwarded-For header (when trust proxy is set).
+
+```javascript
+// X-Forwarded-For: client, proxy1, proxy2
+req.ips // ["client", "proxy1", "proxy2"]
+```
+
+#### req.method
+
+HTTP method of the request.
+
+```javascript
+req.method // "GET", "POST", etc.
+```
+
+#### req.originalUrl
+
+Original request URL (preserves full URL).
+
+```javascript
+// GET /search?q=something
+req.originalUrl // "/search?q=something"
+```
+
+#### req.params
+
+Object containing route parameters.
+
+```javascript
+// Route: /users/:userId/books/:bookId
+// URL: /users/34/books/8989
+req.params // { userId: "34", bookId: "8989" }
+```
+
+**Express 5 Change**:
+- Has null prototype when using string paths
+- Wildcard params are arrays: `req.params.splat // ['foo', 'bar']`
+- Unmatched optional params are omitted (not `undefined`)
+
+#### req.path
+
+Path part of the request URL.
+
+```javascript
+// example.com/users?sort=desc
+req.path // "/users"
+```
+
+#### req.protocol
+
+Request protocol string ("http" or "https").
+
+```javascript
+req.protocol // "https"
+```
+
+#### req.query
+
+Object containing query string parameters.
+
+```javascript
+// GET /search?q=tobi+ferret
+req.query.q // "tobi ferret"
+```
+
+**Express 5 Change**: No longer writable, default parser is "simple" (was "extended").
+
+#### req.route
+
+The currently matched route.
+
+```javascript
+app.get('/user/:id', (req, res) => {
+ console.log(req.route)
+})
+```
+
+#### req.secure
+
+Boolean, true if TLS connection.
+
+```javascript
+req.secure // equivalent to req.protocol === 'https'
+```
+
+#### req.signedCookies
+
+Contains signed cookies (requires cookie-parser).
+
+```javascript
+// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
+req.signedCookies.user // "tobi"
+```
+
+#### req.subdomains
+
+Array of subdomains.
+
+```javascript
+// Host: "tobi.ferrets.example.com"
+req.subdomains // ["ferrets", "tobi"]
+```
+
+#### req.xhr
+
+Boolean, true if X-Requested-With header is "XMLHttpRequest".
+
+```javascript
+req.xhr // true for AJAX requests
+```
+
+### Methods
+
+#### req.accepts(types)
+
+Checks if content types are acceptable based on Accept header.
+
+```javascript
+// Accept: text/html
+req.accepts('html') // "html"
+req.accepts('text/html') // "text/html"
+req.accepts('json') // false
+```
+
+#### req.acceptsCharsets(charset [, ...])
+
+Returns first accepted charset.
+
+```javascript
+req.acceptsCharsets('utf-8', 'iso-8859-1')
+```
+
+#### req.acceptsEncodings(encoding [, ...])
+
+Returns first accepted encoding.
+
+```javascript
+req.acceptsEncodings('gzip', 'deflate')
+```
+
+#### req.acceptsLanguages(lang [, ...])
+
+Returns first accepted language.
+
+```javascript
+req.acceptsLanguages('en', 'es')
+```
+
+#### req.get(field)
+
+Returns the specified HTTP request header (case-insensitive).
+
+```javascript
+req.get('Content-Type') // "text/plain"
+req.get('content-type') // "text/plain"
+```
+
+#### req.is(type)
+
+Returns matching content type if incoming Content-Type matches.
+
+```javascript
+// Content-Type: text/html; charset=utf-8
+req.is('html') // 'html'
+req.is('text/html') // 'text/html'
+req.is('text/*') // 'text/*'
+req.is('json') // false
+```
+
+#### req.range(size[, options])
+
+Parses Range header.
+
+```javascript
+const range = req.range(1000)
+if (range.type === 'bytes') {
+ range.forEach(r => {
+ // r.start, r.end
+ })
+}
+```
+
+---
+
+## Response (res)
+
+The res object represents the HTTP response.
+
+### Properties
+
+#### res.app
+
+Reference to the Express application.
+
+#### res.headersSent
+
+Boolean indicating if headers have been sent.
+
+```javascript
+app.get('/', (req, res) => {
+ console.log(res.headersSent) // false
+ res.send('OK')
+ console.log(res.headersSent) // true
+})
+```
+
+#### res.locals
+
+Local variables scoped to the request, available in templates.
+
+```javascript
+app.use((req, res, next) => {
+ res.locals.user = req.user
+ next()
+})
+```
+
+### Methods
+
+#### res.append(field [, value])
+
+Appends value to HTTP response header.
+
+```javascript
+res.append('Link', [' ', ' '])
+res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly')
+```
+
+#### res.attachment([filename])
+
+Sets Content-Disposition header to "attachment".
+
+```javascript
+res.attachment() // Content-Disposition: attachment
+res.attachment('logo.png') // Content-Disposition: attachment; filename="logo.png"
+```
+
+#### res.cookie(name, value [, options])
+
+Sets a cookie.
+
+```javascript
+res.cookie('name', 'tobi', {
+ domain: '.example.com',
+ path: '/admin',
+ secure: true,
+ httpOnly: true,
+ maxAge: 900000,
+ sameSite: 'strict'
+})
+
+// Signed cookie
+res.cookie('name', 'tobi', { signed: true })
+```
+
+| Option | Type | Description |
+|--------|------|-------------|
+| `domain` | String | Cookie domain |
+| `encode` | Function | Cookie value encoding function |
+| `expires` | Date | Expiry date in GMT |
+| `httpOnly` | Boolean | Only accessible by web server |
+| `maxAge` | Number | Expiry time relative to now in ms |
+| `path` | String | Cookie path (default: "/") |
+| `partitioned` | Boolean | Partitioned storage (CHIPS) |
+| `priority` | String | Cookie priority |
+| `secure` | Boolean | HTTPS only |
+| `signed` | Boolean | Sign the cookie |
+| `sameSite` | Boolean/String | SameSite attribute |
+
+#### res.clearCookie(name [, options])
+
+Clears a cookie.
+
+```javascript
+res.clearCookie('name', { path: '/admin' })
+```
+
+**Express 5 Change**: Ignores `maxAge` and `expires` options.
+
+#### res.download(path [, filename] [, options] [, fn])
+
+Transfers file as attachment.
+
+```javascript
+res.download('/report.pdf')
+res.download('/report.pdf', 'report-2024.pdf')
+res.download('/report.pdf', (err) => {
+ if (err) {
+ // Handle error, check res.headersSent
+ }
+})
+```
+
+#### res.end([data[, encoding]])
+
+Ends the response process.
+
+```javascript
+res.end()
+res.status(404).end()
+```
+
+#### res.format(object)
+
+Performs content negotiation on Accept header.
+
+```javascript
+res.format({
+ 'text/plain': () => res.send('hey'),
+ 'text/html': () => res.send('
hey
'),
+ 'application/json': () => res.send({ message: 'hey' }),
+ default: () => res.status(406).send('Not Acceptable')
+})
+```
+
+#### res.get(field)
+
+Returns the HTTP response header.
+
+```javascript
+res.get('Content-Type') // "text/plain"
+```
+
+#### res.json([body])
+
+Sends a JSON response.
+
+```javascript
+res.json(null)
+res.json({ user: 'tobi' })
+res.status(500).json({ error: 'message' })
+```
+
+#### res.jsonp([body])
+
+Sends JSON response with JSONP support.
+
+```javascript
+// ?callback=foo
+res.jsonp({ user: 'tobi' }) // foo({"user":"tobi"})
+```
+
+#### res.links(links)
+
+Sets Link header.
+
+```javascript
+res.links({
+ next: 'http://api.example.com/users?page=2',
+ last: 'http://api.example.com/users?page=5'
+})
+// Link: ; rel="next", ...
+```
+
+#### res.location(path)
+
+Sets the Location header.
+
+```javascript
+res.location('/foo/bar')
+res.location('http://example.com')
+```
+
+#### res.redirect([status,] path)
+
+Redirects to the specified URL.
+
+```javascript
+res.redirect('/foo/bar')
+res.redirect('http://example.com')
+res.redirect(301, 'http://example.com')
+res.redirect('../login')
+```
+
+**Express 5 Change**: `res.redirect('back')` removed. Use:
+```javascript
+res.redirect(req.get('Referrer') || '/')
+```
+
+#### res.render(view [, locals] [, callback])
+
+Renders a view template.
+
+```javascript
+res.render('index')
+res.render('user', { name: 'Tobi' })
+res.render('index', (err, html) => {
+ if (err) return next(err)
+ res.send(html)
+})
+```
+
+#### res.send([body])
+
+Sends the HTTP response. Body can be Buffer, String, Object, Boolean, or Array.
+
+```javascript
+res.send(Buffer.from('whoop'))
+res.send({ some: 'json' })
+res.send('some html
')
+res.status(404).send('Sorry, not found')
+```
+
+#### res.sendFile(path [, options] [, fn])
+
+Sends a file.
+
+```javascript
+res.sendFile('/path/to/file.pdf')
+res.sendFile('file.pdf', { root: __dirname + '/public' })
+res.sendFile(path, (err) => {
+ if (err) next(err)
+})
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `maxAge` | 0 | Cache-Control max-age in ms |
+| `root` | - | Root directory for relative paths |
+| `lastModified` | true | Set Last-Modified header |
+| `headers` | - | Object of headers to serve |
+| `dotfiles` | "ignore" | Dotfile handling |
+| `acceptRanges` | true | Accept ranged requests |
+| `cacheControl` | true | Set Cache-Control header |
+| `immutable` | false | Immutable directive |
+
+#### res.sendStatus(statusCode)
+
+Sets status code and sends its string representation.
+
+```javascript
+res.sendStatus(200) // 'OK'
+res.sendStatus(403) // 'Forbidden'
+res.sendStatus(404) // 'Not Found'
+res.sendStatus(500) // 'Internal Server Error'
+```
+
+**Express 5 Change**: Only accepts integers 100-999.
+
+#### res.set(field [, value])
+
+Sets response header(s).
+
+```javascript
+res.set('Content-Type', 'text/plain')
+res.set({
+ 'Content-Type': 'text/plain',
+ 'Content-Length': '123',
+ 'ETag': '12345'
+})
+```
+
+#### res.status(code)
+
+Sets the HTTP status code (chainable).
+
+```javascript
+res.status(403).end()
+res.status(400).send('Bad Request')
+res.status(404).sendFile('/absolute/path/to/404.png')
+```
+
+**Express 5 Change**: Only accepts integers 100-999.
+
+#### res.type(type)
+
+Sets Content-Type header.
+
+```javascript
+res.type('.html') // 'text/html'
+res.type('html') // 'text/html'
+res.type('json') // 'application/json'
+res.type('application/json') // 'application/json'
+res.type('png') // 'image/png'
+```
+
+#### res.vary(field)
+
+Adds field to Vary header.
+
+```javascript
+res.vary('User-Agent').render('docs')
+```
+
+**Express 5 Change**: Throws error if field argument is missing.
+
+---
+
+## Router
+
+A router is a mini-application for middleware and routes.
+
+```javascript
+const express = require('express')
+const router = express.Router()
+
+// Middleware specific to this router
+router.use((req, res, next) => {
+ console.log('Time:', Date.now())
+ next()
+})
+
+// Routes
+router.get('/', (req, res) => {
+ res.send('Home page')
+})
+
+router.get('/about', (req, res) => {
+ res.send('About page')
+})
+
+module.exports = router
+```
+
+### Methods
+
+#### router.all(path, [callback, ...] callback)
+
+Matches all HTTP methods.
+
+```javascript
+router.all('/*splat', requireAuthentication)
+```
+
+#### router.METHOD(path, [callback, ...] callback)
+
+Routes HTTP requests (get, post, put, delete, etc.).
+
+```javascript
+router.get('/', (req, res) => res.send('GET'))
+router.post('/', (req, res) => res.send('POST'))
+```
+
+#### router.param(name, callback)
+
+Add callback triggers to route parameters.
+
+```javascript
+router.param('user', (req, res, next, id) => {
+ User.find(id, (err, user) => {
+ if (err) return next(err)
+ req.user = user
+ next()
+ })
+})
+```
+
+#### router.route(path)
+
+Returns single route for chaining.
+
+```javascript
+router.route('/users/:user_id')
+ .all((req, res, next) => {
+ // Runs for all HTTP verbs
+ next()
+ })
+ .get((req, res) => {
+ res.json(req.user)
+ })
+ .put((req, res) => {
+ req.user.name = req.body.name
+ res.json(req.user)
+ })
+ .delete((req, res) => {
+ // Delete user
+ })
+```
+
+#### router.use([path], [function, ...] function)
+
+Uses middleware.
+
+```javascript
+router.use(express.json())
+router.use('/users', usersRouter)
+router.use((req, res, next) => {
+ // Middleware for all routes
+ next()
+})
+```
+
+---
+
+## Path Matching (Express 5)
+
+Express 5 uses updated path-to-regexp syntax:
+
+### Named Parameters
+
+```javascript
+app.get('/users/:id', handler) // /users/123 → { id: '123' }
+app.get('/flights/:from-:to', handler) // /flights/LAX-SFO → { from: 'LAX', to: 'SFO' }
+app.get('/plantae/:genus.:species', handler) // /plantae/Prunus.persica
+```
+
+### Wildcard Parameters (Must Be Named)
+
+```javascript
+// Express 5 - wildcards must be named
+app.get('/*splat', handler) // Matches: /foo, /foo/bar
+app.get('/files/*filepath', handler) // req.params.filepath = ['path', 'to', 'file']
+
+// Optional root path matching
+app.get('/{*splat}', handler) // Matches: /, /foo, /foo/bar
+```
+
+### Optional Parameters (Use Braces)
+
+```javascript
+// Express 5 syntax
+app.get('/:file{.:ext}', handler) // Matches: /image, /image.png
+
+// Unmatched optionals are omitted from req.params (not undefined)
+```
+
+### Regular Expressions in Parameters
+
+```javascript
+app.get('/user/:userId(\\d+)', handler) // Only digits
+```
+
+### Path Arrays
+
+```javascript
+app.get(['/users', '/people'], handler)
diff --git a/.claude/skills/express-skill/references/getting-started/quickstart.md b/.claude/skills/express-skill/references/getting-started/quickstart.md
new file mode 100644
index 0000000..b24eb57
--- /dev/null
+++ b/.claude/skills/express-skill/references/getting-started/quickstart.md
@@ -0,0 +1,436 @@
+# Express.js Quickstart Guide
+
+Get started with Express.js 5.x quickly.
+
+## Requirements
+
+- **Node.js 18 or higher** (required for Express 5)
+
+Check your Node.js version:
+
+```bash
+node --version
+```
+
+## Installation
+
+### Create a New Project
+
+```bash
+mkdir myapp
+cd myapp
+npm init -y
+```
+
+### Install Express
+
+```bash
+npm install express
+```
+
+This installs Express 5.x (the current default).
+
+## Hello World
+
+Create `app.js`:
+
+```javascript
+const express = require('express')
+const app = express()
+const port = 3000
+
+app.get('/', (req, res) => {
+ res.send('Hello World!')
+})
+
+app.listen(port, () => {
+ console.log(`Example app listening on port ${port}`)
+})
+```
+
+Run the app:
+
+```bash
+node app.js
+```
+
+Visit http://localhost:3000 to see "Hello World!"
+
+## Basic Routing
+
+```javascript
+const express = require('express')
+const app = express()
+
+// GET request to the homepage
+app.get('/', (req, res) => {
+ res.send('Hello World!')
+})
+
+// POST request to the homepage
+app.post('/', (req, res) => {
+ res.send('Got a POST request')
+})
+
+// PUT request to /user
+app.put('/user', (req, res) => {
+ res.send('Got a PUT request at /user')
+})
+
+// DELETE request to /user
+app.delete('/user', (req, res) => {
+ res.send('Got a DELETE request at /user')
+})
+
+app.listen(3000)
+```
+
+## Static Files
+
+Serve static files (images, CSS, JavaScript) from a directory:
+
+```javascript
+app.use(express.static('public'))
+```
+
+Files in `public/` are now accessible:
+- `public/images/logo.png` → `http://localhost:3000/images/logo.png`
+- `public/css/style.css` → `http://localhost:3000/css/style.css`
+
+### Virtual Path Prefix
+
+```javascript
+app.use('/static', express.static('public'))
+```
+
+Files are now at:
+- `http://localhost:3000/static/images/logo.png`
+
+### Multiple Static Directories
+
+```javascript
+app.use(express.static('public'))
+app.use(express.static('files'))
+```
+
+Express looks for files in the order directories are added.
+
+## Express Generator
+
+Use the application generator to quickly create an app skeleton:
+
+```bash
+npx express-generator myapp
+cd myapp
+npm install
+npm start
+```
+
+### Generator Options
+
+```bash
+npx express-generator --help
+
+Options:
+ --version output version number
+ -e, --ejs add ejs engine support
+ --pug add pug engine support
+ --hbs add handlebars engine support
+ -H, --hogan add hogan.js engine support
+ --no-view generate without view engine
+ -v, --view add view support (dust|ejs|hbs|hjs|jade|pug|twig|vash)
+ -c, --css add stylesheet support (less|stylus|compass|sass)
+ --git add .gitignore
+ -f, --force force on non-empty directory
+```
+
+### Example with Pug
+
+```bash
+npx express-generator --view=pug myapp
+```
+
+### Generated Structure
+
+```
+myapp/
+├── app.js
+├── bin/
+│ └── www
+├── package.json
+├── public/
+│ ├── images/
+│ ├── javascripts/
+│ └── stylesheets/
+│ └── style.css
+├── routes/
+│ ├── index.js
+│ └── users.js
+└── views/
+ ├── error.pug
+ ├── index.pug
+ └── layout.pug
+```
+
+## JSON API Setup
+
+Common setup for a JSON API:
+
+```javascript
+const express = require('express')
+const app = express()
+
+// Parse JSON bodies
+app.use(express.json())
+
+// Parse URL-encoded bodies
+app.use(express.urlencoded({ extended: true }))
+
+// CORS (if needed)
+app.use((req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
+ next()
+})
+
+// Routes
+app.get('/api/users', (req, res) => {
+ res.json([
+ { id: 1, name: 'Alice' },
+ { id: 2, name: 'Bob' }
+ ])
+})
+
+app.post('/api/users', (req, res) => {
+ const { name } = req.body
+ res.status(201).json({ id: 3, name })
+})
+
+// 404 handler
+app.use((req, res) => {
+ res.status(404).json({ error: 'Not Found' })
+})
+
+// Error handler
+app.use((err, req, res, next) => {
+ console.error(err.stack)
+ res.status(500).json({ error: 'Internal Server Error' })
+})
+
+app.listen(3000, () => {
+ console.log('API running on http://localhost:3000')
+})
+```
+
+## Project Structure
+
+### Simple Structure
+
+```
+myapp/
+├── app.js # App entry point
+├── package.json
+├── public/ # Static files
+└── routes/ # Route handlers
+ ├── index.js
+ └── users.js
+```
+
+### Feature-Based Structure
+
+```
+myapp/
+├── app.js
+├── package.json
+├── config/
+│ └── database.js
+├── middleware/
+│ ├── auth.js
+│ └── errorHandler.js
+├── routes/
+│ ├── index.js
+│ └── api/
+│ ├── users.js
+│ └── posts.js
+├── models/
+│ ├── User.js
+│ └── Post.js
+├── controllers/
+│ ├── userController.js
+│ └── postController.js
+├── services/
+│ └── emailService.js
+├── utils/
+│ └── helpers.js
+├── public/
+└── views/
+```
+
+## Environment Variables
+
+Use environment variables for configuration:
+
+```bash
+npm install dotenv
+```
+
+Create `.env`:
+
+```
+PORT=3000
+NODE_ENV=development
+DATABASE_URL=mongodb://localhost/myapp
+```
+
+Load in `app.js`:
+
+```javascript
+require('dotenv').config()
+
+const port = process.env.PORT || 3000
+const dbUrl = process.env.DATABASE_URL
+```
+
+## Development Tools
+
+### Nodemon (Auto-restart)
+
+```bash
+npm install -D nodemon
+```
+
+Add to `package.json`:
+
+```json
+{
+ "scripts": {
+ "start": "node app.js",
+ "dev": "nodemon app.js"
+ }
+}
+```
+
+Run:
+
+```bash
+npm run dev
+```
+
+### Debug Mode
+
+```bash
+DEBUG=express:* node app.js
+```
+
+## Common Middleware Setup
+
+```javascript
+const express = require('express')
+const helmet = require('helmet')
+const cors = require('cors')
+const morgan = require('morgan')
+const compression = require('compression')
+
+const app = express()
+
+// Security headers
+app.use(helmet())
+
+// CORS
+app.use(cors())
+
+// Logging
+app.use(morgan('dev'))
+
+// Compression
+app.use(compression())
+
+// Body parsing
+app.use(express.json({ limit: '10kb' }))
+app.use(express.urlencoded({ extended: true, limit: '10kb' }))
+
+// Static files
+app.use(express.static('public'))
+```
+
+Install all middleware:
+
+```bash
+npm install helmet cors morgan compression
+```
+
+## TypeScript Setup
+
+```bash
+npm install typescript @types/node @types/express ts-node -D
+npx tsc --init
+```
+
+Create `app.ts`:
+
+```typescript
+import express, { Request, Response, NextFunction } from 'express'
+
+const app = express()
+const port = process.env.PORT || 3000
+
+app.get('/', (req: Request, res: Response) => {
+ res.send('Hello TypeScript!')
+})
+
+app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
+ console.error(err.stack)
+ res.status(500).send('Something broke!')
+})
+
+app.listen(port, () => {
+ console.log(`Server running on port ${port}`)
+})
+```
+
+Add to `package.json`:
+
+```json
+{
+ "scripts": {
+ "build": "tsc",
+ "start": "node dist/app.js",
+ "dev": "ts-node app.ts"
+ }
+}
+```
+
+## ES Modules
+
+To use ES modules (`import`/`export`):
+
+Add to `package.json`:
+
+```json
+{
+ "type": "module"
+}
+```
+
+Update code:
+
+```javascript
+import express from 'express'
+
+const app = express()
+
+app.get('/', (req, res) => {
+ res.send('Hello ES Modules!')
+})
+
+export default app
+```
+
+## Next Steps
+
+1. **Routing** - See `references/guide/routing.md`
+2. **Middleware** - See `references/guide/middleware.md`
+3. **Error Handling** - See `references/guide/error-handling.md`
+4. **Security** - See `references/advanced/security.md`
+5. **Performance** - See `references/advanced/performance.md`
+6. **API Reference** - See `references/api/api-reference.md`
diff --git a/.claude/skills/express-skill/references/guide/error-handling.md b/.claude/skills/express-skill/references/guide/error-handling.md
new file mode 100644
index 0000000..50762c8
--- /dev/null
+++ b/.claude/skills/express-skill/references/guide/error-handling.md
@@ -0,0 +1,441 @@
+# Express Error Handling Guide
+
+Error handling refers to how Express catches and processes errors that occur both synchronously and asynchronously.
+
+## Catching Errors
+
+### Synchronous Errors
+
+Errors in synchronous code are caught automatically:
+
+```javascript
+app.get('/', (req, res) => {
+ throw new Error('BROKEN') // Express will catch this
+})
+```
+
+### Asynchronous Errors (Callback-Style)
+
+Pass errors to `next()` for Express to catch and process:
+
+```javascript
+app.get('/', (req, res, next) => {
+ fs.readFile('/file-does-not-exist', (err, data) => {
+ if (err) {
+ next(err) // Pass errors to Express
+ } else {
+ res.send(data)
+ }
+ })
+})
+```
+
+### Asynchronous Errors (Express 5 - Promises)
+
+**Express 5 automatically handles rejected promises**:
+
+```javascript
+app.get('/user/:id', async (req, res, next) => {
+ const user = await getUserById(req.params.id)
+ res.send(user)
+})
+// If getUserById rejects, next(err) is called automatically
+```
+
+### The next() Function
+
+- `next()` - Pass control to next middleware
+- `next('route')` - Skip to next route handler
+- `next(err)` - Skip to error-handling middleware
+- `next('router')` - Exit the router
+
+Passing anything to `next()` except `'route'` or `'router'` triggers error handling:
+
+```javascript
+app.get('/', (req, res, next) => {
+ next(new Error('Something went wrong'))
+})
+```
+
+### Simplified Error Passing
+
+When the callback only handles errors:
+
+```javascript
+app.get('/', [
+ function (req, res, next) {
+ fs.writeFile('/inaccessible-path', 'data', next)
+ },
+ function (req, res) {
+ res.send('OK')
+ }
+])
+```
+
+### Catching Async Errors in setTimeout/setInterval
+
+You must use try-catch for errors in async operations:
+
+```javascript
+app.get('/', (req, res, next) => {
+ setTimeout(() => {
+ try {
+ throw new Error('BROKEN')
+ } catch (err) {
+ next(err)
+ }
+ }, 100)
+})
+```
+
+### Using Promises
+
+Promises automatically catch both sync errors and rejections:
+
+```javascript
+app.get('/', (req, res, next) => {
+ Promise.resolve()
+ .then(() => {
+ throw new Error('BROKEN')
+ })
+ .catch(next) // Pass to Express error handler
+})
+```
+
+### Chained Error Handling
+
+```javascript
+app.get('/', [
+ function (req, res, next) {
+ fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
+ res.locals.data = data
+ next(err)
+ })
+ },
+ function (req, res) {
+ res.locals.data = res.locals.data.split(',')[1]
+ res.send(res.locals.data)
+ }
+])
+```
+
+## The Default Error Handler
+
+Express has a built-in error handler at the end of the middleware stack:
+
+- Writes error to client with stack trace (development only)
+- Sets `res.statusCode` from `err.status` or `err.statusCode`
+- Sets `res.statusMessage` according to status code
+- In production, only sends status code message (no stack trace)
+
+### Setting Production Mode
+
+```bash
+NODE_ENV=production node app.js
+```
+
+### Error Response Details
+
+When an error is passed to `next()`:
+
+```javascript
+const err = new Error('Not Found')
+err.status = 404
+err.headers = { 'X-Custom-Header': 'value' }
+next(err)
+```
+
+- `err.status` / `err.statusCode` → Response status code (defaults to 500)
+- `err.headers` → Additional response headers
+- `err.stack` → Stack trace (development only)
+
+### Delegating to Default Handler
+
+If headers are already sent, delegate to the default handler:
+
+```javascript
+function errorHandler(err, req, res, next) {
+ if (res.headersSent) {
+ return next(err)
+ }
+ res.status(500)
+ res.render('error', { error: err })
+}
+```
+
+## Writing Error Handlers
+
+Error-handling middleware has **four arguments**:
+
+```javascript
+app.use((err, req, res, next) => {
+ console.error(err.stack)
+ res.status(500).send('Something broke!')
+})
+```
+
+### Placement
+
+Define error handlers **last**, after other `app.use()` and routes:
+
+```javascript
+const bodyParser = require('body-parser')
+const methodOverride = require('method-override')
+
+app.use(bodyParser.urlencoded({ extended: true }))
+app.use(bodyParser.json())
+app.use(methodOverride())
+
+// Routes
+app.use('/api', apiRoutes)
+
+// Error handler - MUST be last
+app.use((err, req, res, next) => {
+ // error handling logic
+})
+```
+
+### Multiple Error Handlers
+
+Chain multiple error handlers for different purposes:
+
+```javascript
+app.use(logErrors)
+app.use(clientErrorHandler)
+app.use(errorHandler)
+```
+
+#### Log Errors
+
+```javascript
+function logErrors(err, req, res, next) {
+ console.error(err.stack)
+ next(err)
+}
+```
+
+#### Handle XHR Errors
+
+```javascript
+function clientErrorHandler(err, req, res, next) {
+ if (req.xhr) {
+ res.status(500).send({ error: 'Something failed!' })
+ } else {
+ next(err)
+ }
+}
+```
+
+#### Catch-All Error Handler
+
+```javascript
+function errorHandler(err, req, res, next) {
+ res.status(500)
+ res.render('error', { error: err })
+}
+```
+
+**Important**: When not calling `next()` in an error handler, you must write and end the response.
+
+## Common Error Handling Patterns
+
+### Custom Error Classes
+
+```javascript
+class AppError extends Error {
+ constructor(message, statusCode) {
+ super(message)
+ this.statusCode = statusCode
+ this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'
+ this.isOperational = true
+
+ Error.captureStackTrace(this, this.constructor)
+ }
+}
+
+// Usage
+app.get('/user/:id', async (req, res, next) => {
+ const user = await User.findById(req.params.id)
+ if (!user) {
+ return next(new AppError('User not found', 404))
+ }
+ res.json(user)
+})
+```
+
+### 404 Handler
+
+Add after all routes:
+
+```javascript
+app.use((req, res, next) => {
+ res.status(404).send("Sorry, can't find that!")
+})
+```
+
+Or throw an error:
+
+```javascript
+app.use((req, res, next) => {
+ const err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+```
+
+### JSON API Error Handler
+
+```javascript
+app.use((err, req, res, next) => {
+ const statusCode = err.statusCode || 500
+ const message = err.message || 'Internal Server Error'
+
+ res.status(statusCode).json({
+ status: 'error',
+ statusCode,
+ message,
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
+ })
+})
+```
+
+### Environment-Specific Errors
+
+```javascript
+app.use((err, req, res, next) => {
+ res.status(err.status || 500)
+
+ if (process.env.NODE_ENV === 'production') {
+ // Production: minimal info
+ res.json({
+ message: err.isOperational ? err.message : 'Something went wrong'
+ })
+ } else {
+ // Development: full details
+ res.json({
+ message: err.message,
+ stack: err.stack,
+ error: err
+ })
+ }
+})
+```
+
+### Async Handler Wrapper
+
+For Express 4 (Express 5 handles this automatically):
+
+```javascript
+const asyncHandler = (fn) => (req, res, next) => {
+ Promise.resolve(fn(req, res, next)).catch(next)
+}
+
+// Usage
+app.get('/user/:id', asyncHandler(async (req, res) => {
+ const user = await User.findById(req.params.id)
+ if (!user) throw new AppError('User not found', 404)
+ res.json(user)
+}))
+```
+
+## Express 5 Async Error Handling
+
+Express 5 automatically catches rejected promises:
+
+```javascript
+// This works in Express 5 without wrapper
+app.get('/user/:id', async (req, res) => {
+ const user = await User.findById(req.params.id)
+ if (!user) {
+ const err = new Error('User not found')
+ err.status = 404
+ throw err // Automatically caught and passed to error handler
+ }
+ res.json(user)
+})
+```
+
+## What NOT to Do
+
+### Don't Listen for uncaughtException
+
+```javascript
+// BAD - Don't do this
+process.on('uncaughtException', (err) => {
+ console.log('Caught exception:', err)
+ // App continues running in unreliable state
+})
+```
+
+This keeps the app running in an unpredictable state. Let it crash and use a process manager to restart.
+
+### Don't Use Domains
+
+The `domain` module is deprecated and doesn't solve the problem properly.
+
+## Best Practices
+
+1. **Use try-catch for sync code** in route handlers
+2. **Use promises** and let Express 5 catch rejections
+3. **Create custom error classes** for operational errors
+4. **Log errors** before responding
+5. **Never expose stack traces** in production
+6. **Use process managers** (PM2, systemd) for restarts
+7. **Define error handlers last** in middleware stack
+8. **Always provide 4 arguments** to error handler
+9. **Delegate to default handler** if headers sent
+10. **Handle 404s explicitly** as the last non-error middleware
+
+## Error Handler Template
+
+```javascript
+// Custom error class
+class AppError extends Error {
+ constructor(message, statusCode) {
+ super(message)
+ this.statusCode = statusCode
+ this.isOperational = true
+ Error.captureStackTrace(this, this.constructor)
+ }
+}
+
+// 404 handler
+app.use((req, res, next) => {
+ next(new AppError(`Can't find ${req.originalUrl}`, 404))
+})
+
+// Global error handler
+app.use((err, req, res, next) => {
+ err.statusCode = err.statusCode || 500
+
+ // Log error
+ console.error('ERROR:', err)
+
+ // Send response
+ if (process.env.NODE_ENV === 'production') {
+ // Production: operational errors only
+ if (err.isOperational) {
+ res.status(err.statusCode).json({
+ status: 'error',
+ message: err.message
+ })
+ } else {
+ // Programming error: don't leak details
+ res.status(500).json({
+ status: 'error',
+ message: 'Something went wrong'
+ })
+ }
+ } else {
+ // Development: full details
+ res.status(err.statusCode).json({
+ status: 'error',
+ message: err.message,
+ stack: err.stack,
+ error: err
+ })
+ }
+})
+
+module.exports = { AppError }
+```
diff --git a/.claude/skills/express-skill/references/guide/middleware.md b/.claude/skills/express-skill/references/guide/middleware.md
new file mode 100644
index 0000000..9882d51
--- /dev/null
+++ b/.claude/skills/express-skill/references/guide/middleware.md
@@ -0,0 +1,438 @@
+# Express Middleware Guide
+
+Express is essentially a series of middleware function calls. Middleware functions have access to the request object (`req`), response object (`res`), and the next middleware function (`next`).
+
+## What Middleware Can Do
+
+- Execute any code
+- Make changes to request and response objects
+- End the request-response cycle
+- Call the next middleware function in the stack
+
+**Important**: If a middleware function does not end the request-response cycle, it must call `next()` to pass control to the next function. Otherwise, the request will hang.
+
+## Types of Middleware
+
+1. Application-level middleware
+2. Router-level middleware
+3. Error-handling middleware
+4. Built-in middleware
+5. Third-party middleware
+
+## Application-Level Middleware
+
+Bind to the app object using `app.use()` and `app.METHOD()`:
+
+### Middleware with No Mount Path
+
+Executed for every request:
+
+```javascript
+const express = require('express')
+const app = express()
+
+app.use((req, res, next) => {
+ console.log('Time:', Date.now())
+ next()
+})
+```
+
+### Middleware Mounted on a Path
+
+Executed for any request to `/user/:id`:
+
+```javascript
+app.use('/user/:id', (req, res, next) => {
+ console.log('Request Type:', req.method)
+ next()
+})
+```
+
+### Route Handler (Middleware System)
+
+```javascript
+app.get('/user/:id', (req, res, next) => {
+ res.send('USER')
+})
+```
+
+### Multiple Middleware Functions
+
+Loading a series of middleware at a mount point:
+
+```javascript
+app.use('/user/:id', (req, res, next) => {
+ console.log('Request URL:', req.originalUrl)
+ next()
+}, (req, res, next) => {
+ console.log('Request Type:', req.method)
+ next()
+})
+```
+
+### Middleware Sub-Stack
+
+```javascript
+app.get('/user/:id', (req, res, next) => {
+ console.log('ID:', req.params.id)
+ next()
+}, (req, res, next) => {
+ res.send('User Info')
+})
+
+// This second handler for the same path will never be called
+// because the first one ends the request-response cycle
+app.get('/user/:id', (req, res, next) => {
+ res.send(req.params.id)
+})
+```
+
+### Skipping Middleware with next('route')
+
+Skip remaining middleware in the current route:
+
+```javascript
+app.get('/user/:id', (req, res, next) => {
+ // if user ID is 0, skip to next route
+ if (req.params.id === '0') next('route')
+ else next()
+}, (req, res, next) => {
+ // render a regular page
+ res.send('regular')
+})
+
+// handler for the /user/:id path
+app.get('/user/:id', (req, res, next) => {
+ res.send('special')
+})
+```
+
+**Note**: `next('route')` only works with `app.METHOD()` or `router.METHOD()` functions.
+
+### Reusable Middleware Array
+
+```javascript
+function logOriginalUrl(req, res, next) {
+ console.log('Request URL:', req.originalUrl)
+ next()
+}
+
+function logMethod(req, res, next) {
+ console.log('Request Type:', req.method)
+ next()
+}
+
+const logStuff = [logOriginalUrl, logMethod]
+
+app.get('/user/:id', logStuff, (req, res, next) => {
+ res.send('User Info')
+})
+```
+
+## Router-Level Middleware
+
+Works the same as application-level middleware, but bound to `express.Router()`:
+
+```javascript
+const express = require('express')
+const app = express()
+const router = express.Router()
+
+// Middleware with no mount path - runs for every request to router
+router.use((req, res, next) => {
+ console.log('Time:', Date.now())
+ next()
+})
+
+// Middleware sub-stack for /user/:id
+router.use('/user/:id', (req, res, next) => {
+ console.log('Request URL:', req.originalUrl)
+ next()
+}, (req, res, next) => {
+ console.log('Request Type:', req.method)
+ next()
+})
+
+// Route handler
+router.get('/user/:id', (req, res, next) => {
+ if (req.params.id === '0') next('route')
+ else next()
+}, (req, res, next) => {
+ res.render('regular')
+})
+
+router.get('/user/:id', (req, res, next) => {
+ console.log(req.params.id)
+ res.render('special')
+})
+
+// Mount the router
+app.use('/', router)
+```
+
+### Skipping Router with next('router')
+
+Skip out of the router instance entirely:
+
+```javascript
+const router = express.Router()
+
+// Predicate router with a check
+router.use((req, res, next) => {
+ if (!req.headers['x-auth']) return next('router')
+ next()
+})
+
+router.get('/user/:id', (req, res) => {
+ res.send('hello, user!')
+})
+
+// Use the router and 401 anything falling through
+app.use('/admin', router, (req, res) => {
+ res.sendStatus(401)
+})
+```
+
+## Error-Handling Middleware
+
+**Error-handling middleware always takes four arguments**. You must provide all four to identify it as error-handling middleware:
+
+```javascript
+app.use((err, req, res, next) => {
+ console.error(err.stack)
+ res.status(500).send('Something broke!')
+})
+```
+
+Define error-handling middleware **last**, after other `app.use()` and routes:
+
+```javascript
+const bodyParser = require('body-parser')
+const methodOverride = require('method-override')
+
+app.use(bodyParser.urlencoded({ extended: true }))
+app.use(bodyParser.json())
+app.use(methodOverride())
+app.use((err, req, res, next) => {
+ // error handling logic
+})
+```
+
+### Multiple Error Handlers
+
+```javascript
+app.use(logErrors)
+app.use(clientErrorHandler)
+app.use(errorHandler)
+
+function logErrors(err, req, res, next) {
+ console.error(err.stack)
+ next(err)
+}
+
+function clientErrorHandler(err, req, res, next) {
+ if (req.xhr) {
+ res.status(500).send({ error: 'Something failed!' })
+ } else {
+ next(err)
+ }
+}
+
+function errorHandler(err, req, res, next) {
+ res.status(500)
+ res.render('error', { error: err })
+}
+```
+
+**Important**: When not calling `next()` in an error handler, you are responsible for writing and ending the response.
+
+## Built-in Middleware
+
+Starting with Express 4.x, Express no longer depends on Connect. Built-in middleware:
+
+| Middleware | Description |
+|------------|-------------|
+| `express.static` | Serves static assets (HTML, images, etc.) |
+| `express.json` | Parses incoming JSON payloads (Express 4.16.0+) |
+| `express.urlencoded` | Parses URL-encoded payloads (Express 4.16.0+) |
+
+```javascript
+app.use(express.json())
+app.use(express.urlencoded({ extended: true }))
+app.use(express.static('public'))
+```
+
+## Third-Party Middleware
+
+Install and load third-party middleware:
+
+```bash
+npm install cookie-parser
+```
+
+```javascript
+const express = require('express')
+const app = express()
+const cookieParser = require('cookie-parser')
+
+// Load the cookie-parsing middleware
+app.use(cookieParser())
+```
+
+### Common Third-Party Middleware
+
+| Package | Purpose |
+|---------|---------|
+| `helmet` | Security headers |
+| `cors` | CORS handling |
+| `morgan` | HTTP request logging |
+| `compression` | Response compression |
+| `cookie-parser` | Cookie parsing |
+| `express-session` | Session management |
+| `passport` | Authentication |
+| `multer` | Multipart/form-data (file uploads) |
+| `express-validator` | Input validation |
+| `express-rate-limit` | Rate limiting |
+
+## Writing Custom Middleware
+
+### Basic Middleware Function
+
+```javascript
+const myLogger = (req, res, next) => {
+ console.log('LOGGED')
+ next()
+}
+
+app.use(myLogger)
+```
+
+### Middleware with Configuration
+
+```javascript
+function requestTime(options = {}) {
+ return (req, res, next) => {
+ req.requestTime = Date.now()
+ if (options.log) {
+ console.log(`Request time: ${req.requestTime}`)
+ }
+ next()
+ }
+}
+
+app.use(requestTime({ log: true }))
+
+app.get('/', (req, res) => {
+ res.send(`Request received at: ${req.requestTime}`)
+})
+```
+
+### Async Middleware (Express 5)
+
+In Express 5, rejected promises are automatically handled:
+
+```javascript
+app.use(async (req, res, next) => {
+ req.user = await getUser(req)
+ next() // Called if promise doesn't reject
+})
+```
+
+### Async Middleware with Validation
+
+```javascript
+const cookieParser = require('cookie-parser')
+const cookieValidator = require('./cookieValidator')
+
+async function validateCookies(req, res, next) {
+ await cookieValidator(req.cookies)
+ next()
+}
+
+app.use(cookieParser())
+app.use(validateCookies)
+
+// Error handler
+app.use((err, req, res, next) => {
+ res.status(400).send(err.message)
+})
+```
+
+## Middleware Order
+
+**Order matters!** Middleware is executed sequentially:
+
+```javascript
+// Logger runs first
+app.use(morgan('combined'))
+
+// Then body parsing
+app.use(express.json())
+
+// Then authentication
+app.use(authMiddleware)
+
+// Then routes
+app.use('/api', apiRoutes)
+
+// 404 handler (must be after all routes)
+app.use((req, res, next) => {
+ res.status(404).send('Not Found')
+})
+
+// Error handler (must be last)
+app.use((err, req, res, next) => {
+ res.status(500).send('Server Error')
+})
+```
+
+### Static Files Before Logger
+
+To skip logging for static files:
+
+```javascript
+// Static files served first, no logging
+app.use(express.static('public'))
+
+// Logger only for non-static requests
+app.use(morgan('combined'))
+```
+
+## Conditional Middleware
+
+### Skip Middleware for Certain Paths
+
+```javascript
+app.use((req, res, next) => {
+ if (req.path.startsWith('/public')) {
+ return next()
+ }
+ // Do middleware logic
+ next()
+})
+```
+
+### Environment-Based Middleware
+
+```javascript
+if (process.env.NODE_ENV === 'development') {
+ app.use(morgan('dev'))
+}
+
+if (process.env.NODE_ENV === 'production') {
+ app.use(compression())
+}
+```
+
+## Middleware Best Practices
+
+1. **Always call `next()`** unless you're ending the response
+2. **Define error handlers last** with all 4 parameters
+3. **Keep middleware focused** - single responsibility
+4. **Handle async errors** - catch and pass to `next(err)`
+5. **Order matters** - place middleware in logical sequence
+6. **Use `next('route')`** to skip to next route handler
+7. **Use `next('router')`** to exit router entirely
+8. **Avoid blocking operations** - use async/await
+9. **Validate input early** - before business logic
+10. **Log errors** before passing to error handler
diff --git a/.claude/skills/express-skill/references/guide/migration-v5.md b/.claude/skills/express-skill/references/guide/migration-v5.md
new file mode 100644
index 0000000..b93cb2a
--- /dev/null
+++ b/.claude/skills/express-skill/references/guide/migration-v5.md
@@ -0,0 +1,462 @@
+# Migrating to Express 5
+
+Express 5 maintains the same basic API as Express 4 but includes breaking changes. This guide covers everything needed to migrate.
+
+## Requirements
+
+- **Node.js 18 or higher** is required for Express 5
+
+## Installation
+
+```bash
+npm install "express@5"
+```
+
+## Automated Migration
+
+Use the Express codemod tool to automatically update code:
+
+```bash
+# Run all codemods
+npx @expressjs/codemod upgrade
+
+# Run a specific codemod
+npx @expressjs/codemod name-of-the-codemod
+```
+
+Available codemods: https://github.com/expressjs/codemod
+
+## Removed Methods and Properties
+
+### app.del() → app.delete()
+
+```javascript
+// Express 4
+app.del('/user/:id', handler)
+
+// Express 5
+app.delete('/user/:id', handler)
+```
+
+### app.param(fn)
+
+The `app.param(fn)` signature for modifying `app.param()` behavior is no longer supported.
+
+### Pluralized Method Names
+
+```javascript
+// Express 4 (deprecated)
+req.acceptsCharset('utf-8')
+req.acceptsEncoding('br')
+req.acceptsLanguage('en')
+
+// Express 5
+req.acceptsCharsets('utf-8')
+req.acceptsEncodings('br')
+req.acceptsLanguages('en')
+```
+
+### Leading Colon in app.param()
+
+The leading colon in parameter names is silently ignored (was deprecated in v4):
+
+```javascript
+// Both work the same in Express 5
+app.param('user', handler)
+app.param(':user', handler) // colon is ignored
+```
+
+### req.param(name)
+
+Removed. Use specific objects instead:
+
+```javascript
+// Express 4
+const id = req.param('id')
+const body = req.param('body')
+const query = req.param('query')
+
+// Express 5
+const id = req.params.id
+const body = req.body
+const query = req.query
+```
+
+### res.json(obj, status)
+
+```javascript
+// Express 4
+res.json({ name: 'Ruben' }, 201)
+
+// Express 5
+res.status(201).json({ name: 'Ruben' })
+```
+
+### res.jsonp(obj, status)
+
+```javascript
+// Express 4
+res.jsonp({ name: 'Ruben' }, 201)
+
+// Express 5
+res.status(201).jsonp({ name: 'Ruben' })
+```
+
+### res.redirect(url, status)
+
+```javascript
+// Express 4
+res.redirect('/users', 301)
+
+// Express 5
+res.redirect(301, '/users')
+```
+
+### res.redirect('back') and res.location('back')
+
+The magic string `'back'` is no longer supported:
+
+```javascript
+// Express 4
+res.redirect('back')
+
+// Express 5
+res.redirect(req.get('Referrer') || '/')
+```
+
+### res.send(body, status)
+
+```javascript
+// Express 4
+res.send({ name: 'Ruben' }, 200)
+
+// Express 5
+res.status(200).send({ name: 'Ruben' })
+```
+
+### res.send(status)
+
+Cannot send a number as the response body:
+
+```javascript
+// Express 4
+res.send(200)
+
+// Express 5
+res.sendStatus(200)
+
+// Or to send a number as the body:
+res.send('200') // String
+```
+
+### res.sendfile() → res.sendFile()
+
+```javascript
+// Express 4
+res.sendfile('/path/to/file')
+
+// Express 5
+res.sendFile('/path/to/file')
+```
+
+**Note**: MIME types have changed in Express 5:
+- `.js` → `"text/javascript"` (was `"application/javascript"`)
+- `.json` → `"application/json"` (was `"text/json"`)
+- `.css` → `"text/css"` (was `"text/plain"`)
+- `.xml` → `"application/xml"` (was `"text/xml"`)
+- `.woff` → `"font/woff"` (was `"application/font-woff"`)
+- `.svg` → `"image/svg+xml"` (was `"application/svg+xml"`)
+
+### router.param(fn)
+
+No longer supported (was deprecated in v4.11.0).
+
+### express.static.mime
+
+Use the `mime-types` package instead:
+
+```javascript
+// Express 4
+express.static.mime.lookup('json')
+
+// Express 5
+const mime = require('mime-types')
+mime.lookup('json')
+```
+
+### express:router Debug Logs
+
+Debug namespace changed:
+
+```bash
+# Express 4
+DEBUG=express:* node index.js
+
+# Express 5
+DEBUG=express:*,router,router:* node index.js
+```
+
+## Changed Behavior
+
+### Path Route Matching Syntax
+
+#### Wildcards Must Be Named
+
+```javascript
+// Express 4
+app.get('/*', handler)
+
+// Express 5
+app.get('/*splat', handler)
+
+// To match root path as well
+app.get('/{*splat}', handler) // Matches /, /foo, /foo/bar
+```
+
+#### Optional Parameters Use Braces
+
+```javascript
+// Express 4
+app.get('/:file.:ext?', handler)
+
+// Express 5
+app.get('/:file{.:ext}', handler)
+```
+
+#### No Regexp Characters in Paths
+
+```javascript
+// Express 4
+app.get('/[discussion|page]/:slug', handler)
+
+// Express 5 - use arrays
+app.get(['/discussion/:slug', '/page/:slug'], handler)
+```
+
+#### Reserved Characters
+
+Characters `()[]?+!` are reserved. Escape with `\`:
+
+```javascript
+app.get('/path\\(with\\)parens', handler)
+```
+
+#### Parameter Names
+
+Support valid JavaScript identifiers or quoted names:
+
+```javascript
+app.get('/:"this"', handler)
+```
+
+### Rejected Promises Handled Automatically
+
+**Major improvement**: No more async wrappers needed:
+
+```javascript
+// Express 5 - just works
+app.get('/user/:id', async (req, res) => {
+ const user = await getUserById(req.params.id) // Errors caught automatically
+ res.send(user)
+})
+```
+
+### express.urlencoded
+
+The `extended` option now defaults to `false`:
+
+```javascript
+// Express 4 default
+app.use(express.urlencoded()) // extended: true
+
+// Express 5 default
+app.use(express.urlencoded()) // extended: false
+
+// To get v4 behavior
+app.use(express.urlencoded({ extended: true }))
+```
+
+### express.static dotfiles
+
+The `dotfiles` option now defaults to `"ignore"`:
+
+```javascript
+// Express 4 - dotfiles served by default
+app.use(express.static('public'))
+
+// Express 5 - dotfiles ignored by default
+// /.well-known/assetlinks.json returns 404
+
+// To serve specific dot-directories
+app.use('/.well-known', express.static('public/.well-known', { dotfiles: 'allow' }))
+app.use(express.static('public'))
+```
+
+### app.listen Error Handling
+
+Errors passed to callback instead of thrown:
+
+```javascript
+// Express 4 - errors thrown
+const server = app.listen(8080, () => {
+ console.log('Listening')
+})
+
+// Express 5 - errors passed to callback
+const server = app.listen(8080, '0.0.0.0', (error) => {
+ if (error) {
+ throw error // e.g., EADDRINUSE
+ }
+ console.log(`Listening on ${JSON.stringify(server.address())}`)
+})
+```
+
+### app.router
+
+The `app.router` object is back (was removed in v4):
+
+```javascript
+const router = app.router // Reference to base Express router
+```
+
+### req.body
+
+Returns `undefined` when body not parsed (was `{}` in v4):
+
+```javascript
+// Express 4
+console.log(req.body) // {} when no body-parser
+
+// Express 5
+console.log(req.body) // undefined when no body-parser
+```
+
+### req.host
+
+Now includes port number:
+
+```javascript
+// Host: "example.com:3000"
+
+// Express 4
+req.host // "example.com"
+
+// Express 5
+req.host // "example.com:3000"
+```
+
+### req.params
+
+**Null prototype** when using string paths:
+
+```javascript
+app.get('/*splat', (req, res) => {
+ console.log(req.params) // [Object: null prototype] { splat: [...] }
+})
+```
+
+**Wildcard parameters are arrays**:
+
+```javascript
+// GET /foo/bar
+req.params.splat // ['foo', 'bar'] - not 'foo/bar'
+```
+
+**Unmatched parameters omitted** (not `undefined`):
+
+```javascript
+// Express 4
+app.get('/:file.:ext?', handler)
+// GET /image → { file: 'image', ext: undefined }
+
+// Express 5
+app.get('/:file{.:ext}', handler)
+// GET /image → { file: 'image' } - no ext key
+```
+
+### req.query
+
+- No longer writable (is a getter)
+- Default parser changed from `"extended"` to `"simple"`
+
+### res.clearCookie
+
+Ignores `maxAge` and `expires` options.
+
+### res.status
+
+Only accepts integers 100-999:
+
+```javascript
+res.status(404) // OK
+res.status('404') // Error
+res.status(99) // Error
+res.status(1000) // Error
+```
+
+### res.vary
+
+Throws error if `field` argument is missing (was a warning in v4).
+
+## Improvements
+
+### res.render()
+
+Now enforces async behavior for all view engines.
+
+### Brotli Encoding Support
+
+Express 5 supports Brotli compression for clients that support it.
+
+## Migration Checklist
+
+### Before Migration
+
+1. [ ] Update to Node.js 18+
+2. [ ] Review current Express 4 deprecation warnings
+3. [ ] Run automated tests
+
+### Code Changes
+
+1. [ ] Replace `app.del()` with `app.delete()`
+2. [ ] Update pluralized method names (`acceptsCharsets`, etc.)
+3. [ ] Replace `req.param()` with specific property access
+4. [ ] Fix response method signatures (`res.json()`, `res.send()`, etc.)
+5. [ ] Replace `res.redirect('back')` with explicit referrer handling
+6. [ ] Update `res.sendfile()` to `res.sendFile()`
+7. [ ] Update wildcard routes (`/*` → `/*splat`)
+8. [ ] Update optional parameters (`?` → braces)
+9. [ ] Replace regexp patterns in paths with arrays
+10. [ ] Update `express.static.mime` usage to `mime-types`
+11. [ ] Handle `req.body` being `undefined` when not parsed
+12. [ ] Handle `app.listen()` errors in callback
+13. [ ] Update debug logging namespace
+
+### Configuration
+
+1. [ ] Check `express.urlencoded({ extended: true })` if needed
+2. [ ] Configure `dotfiles: 'allow'` for `.well-known` if needed
+
+### Testing
+
+1. [ ] Run automated tests
+2. [ ] Test async error handling
+3. [ ] Test all route patterns
+4. [ ] Verify MIME types for static files
+5. [ ] Test error responses
+
+## Quick Reference: Express 4 → 5 Changes
+
+| Feature | Express 4 | Express 5 |
+|---------|-----------|-----------|
+| Async errors | Manual handling | Auto-caught |
+| Wildcard routes | `/*` | `/*splat` |
+| Optional params | `/:file.:ext?` | `/:file{.:ext}` |
+| `app.del()` | Supported | Use `app.delete()` |
+| `res.send(200)` | Sends status | Use `res.sendStatus()` |
+| `res.redirect('back')` | Magic string | Use referrer manually |
+| `req.body` (no parser) | `{}` | `undefined` |
+| `req.host` | Without port | With port |
+| `dotfiles` default | Served | Ignored |
+| `extended` default | `true` | `false` |
+| Node.js version | 0.10+ | 18+ |
diff --git a/.claude/skills/express-skill/references/guide/routing.md b/.claude/skills/express-skill/references/guide/routing.md
new file mode 100644
index 0000000..8a12ccf
--- /dev/null
+++ b/.claude/skills/express-skill/references/guide/routing.md
@@ -0,0 +1,442 @@
+# Express Routing Guide
+
+Routing refers to how an application's endpoints (URIs) respond to client requests.
+
+## Basic Routing
+
+Define routes using methods of the Express app object that correspond to HTTP methods:
+
+```javascript
+const express = require('express')
+const app = express()
+
+// respond with "hello world" when a GET request is made to the homepage
+app.get('/', (req, res) => {
+ res.send('hello world')
+})
+```
+
+## Route Methods
+
+Route methods are derived from HTTP methods and attached to the express instance:
+
+```javascript
+// GET method route
+app.get('/', (req, res) => {
+ res.send('GET request to the homepage')
+})
+
+// POST method route
+app.post('/', (req, res) => {
+ res.send('POST request to the homepage')
+})
+```
+
+Express supports all HTTP request methods: `get`, `post`, `put`, `delete`, `patch`, `options`, `head`, and more.
+
+### app.all() - Match All Methods
+
+Use `app.all()` to load middleware for all HTTP methods at a path:
+
+```javascript
+app.all('/secret', (req, res, next) => {
+ console.log('Accessing the secret section...')
+ next() // pass control to the next handler
+})
+```
+
+## Route Paths
+
+Route paths define endpoints where requests can be made. They can be strings, string patterns, or regular expressions.
+
+### String Paths
+
+```javascript
+// Matches root route /
+app.get('/', (req, res) => res.send('root'))
+
+// Matches /about
+app.get('/about', (req, res) => res.send('about'))
+
+// Matches /random.text
+app.get('/random.text', (req, res) => res.send('random.text'))
+```
+
+### Regular Expression Paths
+
+```javascript
+// Matches anything with an "a" in it
+app.get(/a/, (req, res) => res.send('/a/'))
+
+// Matches butterfly and dragonfly, but not butterflyman
+app.get(/.*fly$/, (req, res) => res.send('/.*fly$/'))
+```
+
+### Express 5 Path Syntax Changes
+
+**Important**: Express 5 uses different path syntax than Express 4.
+
+#### Wildcards Must Be Named
+
+```javascript
+// Express 4 (deprecated)
+app.get('/*', handler)
+
+// Express 5 - wildcards must have names
+app.get('/*splat', handler)
+
+// To also match the root path, wrap in braces
+app.get('/{*splat}', handler) // Matches /, /foo, /foo/bar
+```
+
+#### Optional Parameters Use Braces
+
+```javascript
+// Express 4 (deprecated)
+app.get('/:file.:ext?', handler)
+
+// Express 5
+app.get('/:file{.:ext}', handler)
+```
+
+#### No Regexp Characters in Paths
+
+```javascript
+// Express 4 (deprecated)
+app.get('/[discussion|page]/:slug', handler)
+
+// Express 5 - use arrays instead
+app.get(['/discussion/:slug', '/page/:slug'], handler)
+```
+
+## Route Parameters
+
+Route parameters are named URL segments that capture values at their position in the URL:
+
+```javascript
+// Route path: /users/:userId/books/:bookId
+// Request URL: http://localhost:3000/users/34/books/8989
+// req.params: { "userId": "34", "bookId": "8989" }
+
+app.get('/users/:userId/books/:bookId', (req, res) => {
+ res.send(req.params)
+})
+```
+
+### Parameter Names
+
+Parameter names must be "word characters" `[A-Za-z0-9_]`.
+
+Hyphens and dots are interpreted literally, so they can be used:
+
+```javascript
+// Route path: /flights/:from-:to
+// Request URL: /flights/LAX-SFO
+// req.params: { "from": "LAX", "to": "SFO" }
+
+// Route path: /plantae/:genus.:species
+// Request URL: /plantae/Prunus.persica
+// req.params: { "genus": "Prunus", "species": "persica" }
+```
+
+### Parameter Constraints (Regular Expressions)
+
+Append a regular expression in parentheses:
+
+```javascript
+// Route path: /user/:userId(\d+)
+// Only matches numeric user IDs
+// Request URL: /user/42
+// req.params: { "userId": "42" }
+
+app.get('/user/:userId(\\d+)', (req, res) => {
+ res.send(req.params)
+})
+```
+
+**Note**: Escape backslashes in the regex string: `\\d+`
+
+### Express 5 Parameter Behavior
+
+Wildcard parameters are now arrays:
+
+```javascript
+app.get('/*splat', (req, res) => {
+ // GET /foo/bar
+ console.log(req.params.splat) // ['foo', 'bar']
+})
+```
+
+Unmatched optional parameters are omitted (not `undefined`):
+
+```javascript
+app.get('/:file{.:ext}', (req, res) => {
+ // GET /image
+ console.log(req.params) // { file: 'image' } - no ext key
+})
+```
+
+## Route Handlers
+
+You can provide multiple callback functions that behave like middleware:
+
+### Single Callback
+
+```javascript
+app.get('/example/a', (req, res) => {
+ res.send('Hello from A!')
+})
+```
+
+### Multiple Callbacks
+
+```javascript
+app.get('/example/b', (req, res, next) => {
+ console.log('the response will be sent by the next function...')
+ next()
+}, (req, res) => {
+ res.send('Hello from B!')
+})
+```
+
+### Array of Callbacks
+
+```javascript
+const cb0 = (req, res, next) => {
+ console.log('CB0')
+ next()
+}
+
+const cb1 = (req, res, next) => {
+ console.log('CB1')
+ next()
+}
+
+const cb2 = (req, res) => {
+ res.send('Hello from C!')
+}
+
+app.get('/example/c', [cb0, cb1, cb2])
+```
+
+### Combination of Functions and Arrays
+
+```javascript
+app.get('/example/d', [cb0, cb1], (req, res, next) => {
+ console.log('the response will be sent by the next function...')
+ next()
+}, (req, res) => {
+ res.send('Hello from D!')
+})
+```
+
+### Using next('route')
+
+Skip to the next route by calling `next('route')`:
+
+```javascript
+app.get('/user/:id', (req, res, next) => {
+ // if the user ID is 0, skip to the next route
+ if (req.params.id === '0') next('route')
+ // otherwise pass the control to the next middleware
+ else next()
+}, (req, res) => {
+ // send a regular response
+ res.send('regular')
+})
+
+// handler for the /user/:id path, which sends a special response
+app.get('/user/:id', (req, res) => {
+ res.send('special')
+})
+```
+
+Result:
+- `GET /user/5` → "regular"
+- `GET /user/0` → "special" (first route calls `next('route')`)
+
+## Response Methods
+
+Methods on the response object that send a response and terminate the request-response cycle:
+
+| Method | Description |
+|--------|-------------|
+| `res.download()` | Prompt a file to be downloaded |
+| `res.end()` | End the response process |
+| `res.json()` | Send a JSON response |
+| `res.jsonp()` | Send a JSON response with JSONP support |
+| `res.redirect()` | Redirect a request |
+| `res.render()` | Render a view template |
+| `res.send()` | Send a response of various types |
+| `res.sendFile()` | Send a file as an octet stream |
+| `res.sendStatus()` | Set status code and send its string representation |
+
+**Important**: If none of these methods are called, the client request will be left hanging.
+
+## app.route()
+
+Create chainable route handlers for a route path:
+
+```javascript
+app.route('/book')
+ .get((req, res) => {
+ res.send('Get a random book')
+ })
+ .post((req, res) => {
+ res.send('Add a book')
+ })
+ .put((req, res) => {
+ res.send('Update the book')
+ })
+```
+
+## express.Router
+
+Create modular, mountable route handlers. A Router instance is a complete middleware and routing system (often called a "mini-app").
+
+### Creating a Router Module
+
+Create `birds.js`:
+
+```javascript
+const express = require('express')
+const router = express.Router()
+
+// middleware specific to this router
+const timeLog = (req, res, next) => {
+ console.log('Time:', Date.now())
+ next()
+}
+router.use(timeLog)
+
+// define the home page route
+router.get('/', (req, res) => {
+ res.send('Birds home page')
+})
+
+// define the about route
+router.get('/about', (req, res) => {
+ res.send('About birds')
+})
+
+module.exports = router
+```
+
+### Mounting the Router
+
+```javascript
+const birds = require('./birds')
+
+// ...
+
+app.use('/birds', birds)
+```
+
+The app now handles requests to `/birds` and `/birds/about`.
+
+### mergeParams Option
+
+If the parent route has path parameters, make them accessible in sub-routes:
+
+```javascript
+const router = express.Router({ mergeParams: true })
+```
+
+## Route Organization Patterns
+
+### By Resource (RESTful)
+
+```
+routes/
+├── users.js # /api/users routes
+├── posts.js # /api/posts routes
+├── comments.js # /api/comments routes
+└── index.js # Mount all routes
+```
+
+```javascript
+// routes/users.js
+const router = require('express').Router()
+
+router.get('/', listUsers)
+router.get('/:id', getUser)
+router.post('/', createUser)
+router.put('/:id', updateUser)
+router.delete('/:id', deleteUser)
+
+module.exports = router
+
+// routes/index.js
+const router = require('express').Router()
+
+router.use('/users', require('./users'))
+router.use('/posts', require('./posts'))
+router.use('/comments', require('./comments'))
+
+module.exports = router
+
+// app.js
+app.use('/api', require('./routes'))
+```
+
+### By Feature/Module
+
+```
+modules/
+├── auth/
+│ ├── routes.js
+│ ├── controller.js
+│ └── middleware.js
+├── users/
+│ ├── routes.js
+│ ├── controller.js
+│ └── model.js
+└── posts/
+ ├── routes.js
+ ├── controller.js
+ └── model.js
+```
+
+## Common Routing Patterns
+
+### API Versioning
+
+```javascript
+const v1Router = express.Router()
+const v2Router = express.Router()
+
+v1Router.get('/users', v1ListUsers)
+v2Router.get('/users', v2ListUsers)
+
+app.use('/api/v1', v1Router)
+app.use('/api/v2', v2Router)
+```
+
+### Nested Resources
+
+```javascript
+// /users/:userId/posts/:postId
+const postsRouter = express.Router({ mergeParams: true })
+
+postsRouter.get('/', (req, res) => {
+ // req.params.userId available from parent
+ res.send(`Posts for user ${req.params.userId}`)
+})
+
+postsRouter.get('/:postId', (req, res) => {
+ res.send(`Post ${req.params.postId} for user ${req.params.userId}`)
+})
+
+app.use('/users/:userId/posts', postsRouter)
+```
+
+### 404 Handler
+
+Add at the end of all routes:
+
+```javascript
+// After all other routes
+app.use((req, res) => {
+ res.status(404).send("Sorry, can't find that!")
+})
+```
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
new file mode 100644
index 0000000..36cfae7
--- /dev/null
+++ b/.github/workflows/e2e-tests.yml
@@ -0,0 +1,329 @@
+name: E2E Test Binaries
+
+on:
+ push:
+ branches: ['**']
+ pull_request:
+ branches: ['**']
+ workflow_dispatch:
+
+jobs:
+ test:
+ name: Test ${{ matrix.platform }} ${{ matrix.arch }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ max-parallel: 6
+ matrix:
+ include:
+ - os: windows-latest
+ platform: win
+ arch: x64
+ output: flashforge-webui-win-x64.exe
+ can_execute: true
+ - os: macos-15-intel
+ platform: mac
+ arch: x64
+ output: flashforge-webui-macos-x64.bin
+ can_execute: true
+ - os: macos-latest
+ platform: mac
+ arch: arm64
+ output: flashforge-webui-macos-arm64.bin
+ can_execute: true
+ - os: ubuntu-latest
+ platform: linux
+ arch: x64
+ output: flashforge-webui-linux-x64.bin
+ can_execute: true
+ - os: ubuntu-24.04-arm
+ platform: linux
+ arch: arm64
+ output: flashforge-webui-linux-arm64.bin
+ can_execute: true
+ - os: ubuntu-latest
+ platform: linux
+ arch: armv7
+ output: flashforge-webui-linux-armv7.bin
+ can_execute: false
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js 20
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+
+ - name: Cache pkg fetch
+ uses: actions/cache@v4
+ with:
+ path: ~/.pkg-cache
+ key: ${{ runner.os }}-pkg-${{ hashFiles('package.json') }}
+ restore-keys: |
+ ${{ runner.os }}-pkg-
+
+ - name: Configure GitHub Packages
+ shell: bash
+ run: |
+ echo "@ghosttypes:registry=https://npm.pkg.github.com" >> .npmrc
+ echo "@parallel-7:registry=https://npm.pkg.github.com" >> .npmrc
+ echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ run: npm ci
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Pre-download ARMv7 Node.js Binary
+ if: matrix.arch == 'armv7'
+ run: |
+ mkdir -p ~/.pkg-cache/v3.5
+ curl -L -o ~/.pkg-cache/v3.5/fetched-v20.18.0-linuxstatic-armv7 \
+ https://github.com/yao-pkg/pkg-binaries/releases/download/node20/node-v20.18.0-linuxstatic-armv7
+ chmod +x ~/.pkg-cache/v3.5/fetched-v20.18.0-linuxstatic-armv7
+
+ - name: Build application
+ shell: bash
+ run: |
+ npm run build
+ if [[ "${{ matrix.platform }}" == "win" ]]; then
+ npx @yao-pkg/pkg . --targets node20-win-${{ matrix.arch }} --output dist/${{ matrix.output }}
+ elif [[ "${{ matrix.platform }}" == "mac" ]]; then
+ npx @yao-pkg/pkg . --targets node20-macos-${{ matrix.arch }} --output dist/${{ matrix.output }}
+ elif [[ "${{ matrix.arch }}" == "armv7" ]]; then
+ npx @yao-pkg/pkg . --targets node20-linuxstatic-armv7 --output dist/${{ matrix.output }}
+ else
+ npx @yao-pkg/pkg . --targets node20-linux-${{ matrix.arch }} --output dist/${{ matrix.output }}
+ fi
+
+ - name: Verify binary size
+ shell: bash
+ run: |
+ if [[ "${{ runner.os }}" == "macOS" ]]; then
+ size=$(stat -f%z "dist/${{ matrix.output }}")
+ elif [[ "${{ runner.os }}" == "Windows" ]]; then
+ size=$(powershell -Command "(Get-Item 'dist/${{ matrix.output }}').length")
+ else
+ size=$(stat -c%s "dist/${{ matrix.output }}")
+ fi
+
+ if [ $size -lt 40000000 ]; then
+ echo "::error::Binary size ($size bytes) is too small - assets may not be embedded"
+ exit 1
+ fi
+ echo "✓ Binary size: $size bytes"
+
+ # Windows: use cmd wrapper to redirect output while keeping process detached
+ - name: Start binary (Windows)
+ if: matrix.can_execute == true && runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $proc = Start-Process -FilePath "cmd.exe" `
+ -ArgumentList "/c .\dist\${{ matrix.output }} --no-printers > startup.log 2> startup-err.log" `
+ -WindowStyle Hidden -PassThru
+ $proc.Id | Out-File -FilePath server.pid -Encoding ascii
+ Write-Host "Started server with PID: $($proc.Id)"
+
+ # Unix: use standard background process
+ - name: Start binary (Unix)
+ if: matrix.can_execute == true && runner.os != 'Windows'
+ shell: bash
+ run: |
+ chmod +x dist/${{ matrix.output }}
+ ./dist/${{ matrix.output }} --no-printers > startup.log 2>&1 &
+ echo $! > server.pid
+
+ - name: Wait for server to be ready
+ if: matrix.can_execute == true
+ shell: bash
+ run: |
+ max_attempts=30
+ attempt=0
+ until curl -sf http://127.0.0.1:3000/ > /dev/null 2>&1; do
+ attempt=$((attempt + 1))
+ if [ $attempt -ge $max_attempts ]; then
+ echo "::error::Server failed to start after $max_attempts attempts (60s)"
+ echo "--- startup.log ---"
+ cat startup.log 2>/dev/null || echo "(no stdout log)"
+ echo "--- startup-err.log ---"
+ cat startup-err.log 2>/dev/null || echo "(no stderr log)"
+ exit 1
+ fi
+ echo "Waiting for server... (attempt $attempt/$max_attempts)"
+ sleep 2
+ done
+ echo "✓ Server is responding"
+
+ - name: Validate startup logs
+ if: matrix.can_execute == true
+ shell: bash
+ run: |
+ if [ -f startup.log ]; then
+ if grep -iE "\[Error\]|\[Fatal\]|exception|EADDRINUSE" startup.log; then
+ echo "::error::Errors detected in startup log"
+ cat startup.log
+ exit 1
+ fi
+
+ if ! grep -q "\[Ready\] FlashForgeWebUI is ready" startup.log; then
+ echo "::error::Startup did not complete - missing ready marker"
+ cat startup.log
+ exit 1
+ fi
+ echo "✓ Startup log looks clean"
+ else
+ echo "::warning::No startup.log found"
+ fi
+
+ - name: Test static file serving
+ if: matrix.can_execute == true
+ shell: bash
+ run: |
+ # Test index.html is served at root
+ status=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/)
+ if [ "$status" != "200" ]; then
+ echo "::error::GET / returned HTTP $status (expected 200)"
+ exit 1
+ fi
+
+ # Test index.html contains expected content
+ if ! curl -sf http://127.0.0.1:3000/ | grep -q "FlashForge Web UI"; then
+ echo "::error::GET / did not contain 'FlashForge Web UI'"
+ curl -s http://127.0.0.1:3000/ | head -20
+ exit 1
+ fi
+ echo "✓ Static file serving works"
+
+ - name: Test API endpoints
+ if: matrix.can_execute == true
+ shell: bash
+ run: |
+ # Auth status endpoint
+ status=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/api/auth/status)
+ if [ "$status" != "200" ]; then
+ echo "::error::GET /api/auth/status returned HTTP $status (expected 200)"
+ exit 1
+ fi
+
+ response=$(curl -sf http://127.0.0.1:3000/api/auth/status)
+ if ! echo "$response" | jq '.' > /dev/null 2>&1; then
+ echo "::error::Auth status API returned invalid JSON: $response"
+ exit 1
+ fi
+ echo "✓ GET /api/auth/status - valid JSON response"
+
+ # Login endpoint
+ login_response=$(curl -s -w "\n%{http_code}" -X POST http://127.0.0.1:3000/api/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{"password":"changeme"}')
+ login_body=$(echo "$login_response" | sed '$d')
+ login_status=$(echo "$login_response" | tail -1)
+
+ if [ "$login_status" != "200" ]; then
+ echo "::error::POST /api/auth/login returned HTTP $login_status (expected 200)"
+ echo "Response: $login_body"
+ exit 1
+ fi
+
+ success=$(echo "$login_body" | jq -r '.success')
+ if [ "$success" != "true" ]; then
+ echo "::error::Login API returned success=$success (expected true)"
+ echo "Response: $login_body"
+ exit 1
+ fi
+ echo "✓ POST /api/auth/login - login successful"
+
+ # 404 handler for unknown API routes
+ unknown_status=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/api/nonexistent)
+ if [ "$unknown_status" != "404" ]; then
+ echo "::error::GET /api/nonexistent returned HTTP $unknown_status (expected 404)"
+ exit 1
+ fi
+ echo "✓ GET /api/nonexistent - returns 404"
+
+ echo "✓ All API tests passed"
+
+ # Windows cleanup
+ - name: Stop binary (Windows)
+ if: always() && matrix.can_execute == true && runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ if (Test-Path server.pid) {
+ $serverPid = (Get-Content server.pid).Trim()
+ $proc = Get-Process -Id $serverPid -ErrorAction SilentlyContinue
+ if ($proc) {
+ Stop-Process -Id $serverPid -Force
+ Write-Host "Stopped server (PID: $serverPid)"
+ } else {
+ Write-Host "Process already exited"
+ }
+ }
+ # Fallback: kill by exact image name
+ taskkill /F /IM "${{ matrix.output }}" 2>$null
+ Start-Sleep -Seconds 3
+
+ # Unix cleanup
+ - name: Stop binary (Unix)
+ if: always() && matrix.can_execute == true && runner.os != 'Windows'
+ shell: bash
+ run: |
+ if [ -f server.pid ]; then
+ kill -TERM $(cat server.pid) 2>/dev/null || true
+ rm server.pid
+ fi
+ pkill -TERM -f "${{ matrix.output }}" 2>/dev/null || true
+ sleep 3
+
+ - name: Verify cleanup
+ if: always() && matrix.can_execute == true
+ shell: bash
+ run: |
+ if [[ "${{ runner.os }}" == "Windows" ]]; then
+ if tasklist /FI "IMAGENAME eq ${{ matrix.output }}" 2>/dev/null | grep -q "${{ matrix.output }}"; then
+ echo "::error::Binary left zombie processes"
+ exit 1
+ fi
+ else
+ if pgrep -f "${{ matrix.output }}"; then
+ echo "::error::Binary left zombie processes"
+ exit 1
+ fi
+ fi
+ echo "✓ Cleanup successful"
+
+ - name: Upload test binary
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: ${{ matrix.output }}
+ path: dist/${{ matrix.output }}
+ retention-days: 7
+ compression-level: 6
+
+ - name: Upload startup logs
+ uses: actions/upload-artifact@v4
+ if: always() && matrix.can_execute == true
+ with:
+ name: logs-${{ matrix.platform }}-${{ matrix.arch }}
+ path: |
+ startup.log
+ startup-err.log
+ if-no-files-found: ignore
+ retention-days: 7
+
+ - name: Generate summary
+ if: always()
+ shell: bash
+ run: |
+ echo "## ${{ matrix.platform }} ${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [[ "${{ job.status }}" == "success" ]]; then
+ echo "✅ All tests passed" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Tests failed" >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/CLAUDE.md b/CLAUDE.md
index 7dce1cc..0baff91 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,9 +4,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
-FlashForgeWebUI is a standalone web-based interface for controlling and monitoring FlashForge 3D printers. It was ported from the FlashForgeUI-Electron project (located at `C:\Users\Cope\Documents\GitHub\FlashForgeUI-Electron`) to create a lightweight deployment option for low-spec devices like Raspberry Pi, without Electron dependencies.
+FlashForgeWebUI is a standalone web-based interface for controlling and monitoring FlashForge 3D printers. It provides a lightweight deployment option for low-spec devices like Raspberry Pi, without Electron dependencies.
-**Current Status**: Initial porting is complete but not fully tested. Some bugs are expected.
+**Current Status**: Production-ready. Core functionality tested and working including multi-printer support, Spoolman integration, and cross-platform binary distribution.
## Build & Development Commands
@@ -278,19 +278,19 @@ class Service extends EventEmitter {
## Testing Notes
-Initial porting is complete but **not fully tested**. Known areas to test:
+Core functionality has been tested and verified:
- Multi-printer context switching
-- Camera proxy stability under load
-- RTSP streaming for supported printers
- Spoolman integration (filament tracking)
-- Print state monitoring and notifications
-- Temperature anomaly detection
-- Different printer model backends (AD5X, 5M, 5M Pro, legacy)
+- Platform-specific binary builds (Linux ARM, Linux x64, Windows, macOS)
- WebUI authentication
-- Platform-specific builds (Linux ARM, Windows, macOS)
+- Static file serving in packaged binaries
+
+Areas for continued testing:
+- Camera proxy stability under extended load
+- RTSP streaming for all supported printers
+- Temperature anomaly detection edge cases
## Related Projects
-- **FlashForgeUI-Electron**: Parent project with full Electron desktop app (`C:\Users\Cope\Documents\GitHub\FlashForgeUI-Electron`)
- **@ghosttypes/ff-api**: FlashForge API client library (public package)
- **@parallel-7/slicer-meta**: Printer metadata and model utilities (public package)
diff --git a/README.md b/README.md
index b6d5bd9..ae3a261 100644
--- a/README.md
+++ b/README.md
@@ -122,61 +122,159 @@ FlashForge WebUI supports a wide range of FlashForge printers through its adapta
+**Pre-built Binaries**
+
+
+
+Download the appropriate binary for your platform from the [Releases](https://github.com/Parallel-7/FlashForgeWebUI/releases) page:
+
+| Platform | Binary | Notes |
+|----------|--------|-------|
+| Windows x64 | `flashforge-webui-win-x64.exe` | Most Windows PCs |
+| macOS x64 | `flashforge-webui-macos-x64` | Intel Macs |
+| macOS ARM | `flashforge-webui-macos-arm64` | Apple Silicon (M1/M2/M3) |
+| Linux x64 | `flashforge-webui-linux-x64` | Most Linux PCs |
+| Linux ARM64 | `flashforge-webui-linux-arm64` | Raspberry Pi 4/5 (64-bit OS) |
+| Linux ARMv7 | `flashforge-webui-linux-armv7` | Raspberry Pi 3/4 (32-bit OS) |
+
+**Raspberry Pi Users:** Use `flashforge-webui-linux-arm64` for 64-bit Raspberry Pi OS, or `flashforge-webui-linux-armv7` for 32-bit.
+
+```bash
+# Make the binary executable (Linux/macOS)
+chmod +x flashforge-webui-linux-arm64
+
+# Run with auto-connect to last used printer
+./flashforge-webui-linux-arm64 --last-used
+
+# Run without auto-connect
+./flashforge-webui-linux-arm64 --no-printers
+```
+
+
+
**Running from Source**
```bash
# Clone the repository
-git clone https://github.com/Parallel-7/flashforge-webui.git
-cd flashforge-webui
+git clone https://github.com/Parallel-7/FlashForgeWebUI.git
+cd FlashForgeWebUI
# Install dependencies
npm install
-# Build the application
+# Build the application (required before first run)
npm run build
# Start the server
npm start
+
+# Or start with auto-connect to last used printer
+npm start -- --last-used
+```
+
+**Development Mode:**
+```bash
+# Build and watch for changes with hot reload
+npm run dev
```
+
+
Usage
+
+
+After starting the server, open your browser and navigate to:
+
+```
+http://localhost:3000
+```
+
+Or if accessing from another device on your network:
+
+```
+http://:3000
+```
+
+**Default Login:** The default password is `changeme`. You should change this in `data/config.json` or via the `--webui-password` flag.
+
+
+
Command Line Options
+
+
+| Option | Description |
+|--------|-------------|
+| `--last-used` | Connect to the last used printer on startup |
+| `--all-saved-printers` | Connect to all saved printers on startup |
+| `--printers="IP:TYPE:CODE,..."` | Connect to specific printers (TYPE: "new" or "legacy") |
+| `--no-printers` | Start WebUI only, without connecting to any printer |
+| `--webui-port=PORT` | Override the WebUI port (default: 3000) |
+| `--webui-password=PASS` | Override the WebUI password |
+
Configuration
-The application automatically creates a configuration file at `data/config.json` on first run. You can modify this file to customize your experience.
+The application automatically creates a configuration file at `data/config.json` on first run.
-```json
-{
- "WebUIEnabled": true,
- "WebUIPort": 3000,
- "WebUIPassword": "changeme",
- "WebUIPasswordRequired": true,
- "SpoolmanEnabled": false,
- "SpoolmanServerUrl": "http://your-spoolman-instance:7912",
- "CameraProxyPort": 8181
-}
-```
+| Setting | Default | Description |
+|---------|---------|-------------|
+| `WebUIEnabled` | `true` | Enable/disable the web interface |
+| `WebUIPort` | `3000` | Port for the web server |
+| `WebUIPassword` | `changeme` | Login password (change this!) |
+| `WebUIPasswordRequired` | `true` | Require password to access |
+| `SpoolmanEnabled` | `false` | Enable Spoolman integration |
+| `SpoolmanServerUrl` | `""` | Your Spoolman server URL (e.g., `http://192.168.1.100:7912`) |
+| `CameraProxyPort` | `8181` | Starting port for camera proxies |
-
Development
+ Building from Source
```bash
-# Start development server with hot-reload
-npm run dev
-
# Build for specific platform
-npm run build:linux
-npm run build:win
-npm run build:mac
+npm run build:linux # Linux x64
+npm run build:linux-arm # Linux ARM64 (Raspberry Pi 4/5)
+npm run build:linux-armv7 # Linux ARMv7 (Raspberry Pi 3)
+npm run build:win # Windows x64
+npm run build:mac # macOS x64
+npm run build:mac-arm # macOS ARM (Apple Silicon)
```
+
+
Troubleshooting
+
+
+**"Cannot GET /" or blank page when accessing WebUI:**
+- If running from source: Make sure you ran `npm run build` before `npm start`
+- If using a pre-1.0.2 binary: Update to version 1.0.2 or later (fixes static file serving bug)
+
+**"Permission denied" when running binary:**
+```bash
+chmod +x flashforge-webui-linux-*
+```
+
+**Port already in use:**
+- Change the port in `data/config.json` or use `--webui-port=3001`
+
+**Cannot connect to printer:**
+- Ensure your printer is on the same network as the device running WebUI
+- Check that the printer's IP address is correct
+- For legacy printers, ensure TCP port 8899 is accessible
+
+**Selecting the correct binary for your platform:**
+- Windows: `flashforge-webui-win-x64.exe`
+- macOS Intel: `flashforge-webui-macos-x64`
+- macOS Apple Silicon: `flashforge-webui-macos-arm64`
+- Linux x64: `flashforge-webui-linux-x64`
+- Raspberry Pi (64-bit OS): `flashforge-webui-linux-arm64`
+- Raspberry Pi (32-bit OS): `flashforge-webui-linux-armv7`
+- Check your architecture with `uname -m` (x86_64 = x64, aarch64 = ARM64, armv7l = ARMv7)
+
License
diff --git a/eslint.config.mjs b/eslint.config.mjs
index cebb64b..81c78a3 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -6,11 +6,19 @@ import globals from 'globals';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
+
+ // Backend configuration - uses main tsconfig.json
{
- files: ['**/*.ts', '**/*.tsx'],
+ files: ['src/**/*.ts', '**/*.ts'],
+ ignores: [
+ 'src/webui/static/**/*.ts',
+ 'dist/**',
+ 'node_modules/**',
+ '.dependencies/**'
+ ],
languageOptions: {
ecmaVersion: 2022,
- sourceType: 'module',
+ sourceType: 'commonjs',
globals: {
...globals.node,
},
@@ -29,7 +37,29 @@ export default tseslint.config(
],
},
},
+
+ // Frontend configuration - uses webui static tsconfig
{
- ignores: ['dist/**', 'node_modules/**', '.dependencies/**'],
+ files: ['src/webui/static/**/*.ts'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module',
+ globals: {
+ ...globals.browser,
+ },
+ parserOptions: {
+ project: './src/webui/static/tsconfig.json',
+ },
+ },
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ },
+ ],
+ },
}
);
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..b8f6bdc
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,42 @@
+/**
+ * Jest configuration for FlashForgeWebUI
+ */
+
+module.exports = {
+ preset: 'ts-jest/presets/default-esm',
+ testEnvironment: 'node',
+ roots: ['/src'],
+ testMatch: [
+ '**/?(*.)+(spec|test).ts'
+ ],
+ testPathIgnorePatterns: [
+ '/src/__tests__/setup.ts'
+ ],
+ transform: {
+ '^.+\\.ts$': ['ts-jest', {
+ useESM: true,
+ }],
+ },
+ collectCoverageFrom: [
+ 'src/**/*.ts',
+ '!src/**/*.d.ts',
+ '!src/webui/static/**/*.ts',
+ '!src/**/*.test.ts',
+ '!src/**/*.spec.ts',
+ '!src/__tests__/**',
+ ],
+ coverageDirectory: 'coverage',
+ coverageReporters: [
+ 'text',
+ 'lcov',
+ 'html'
+ ],
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
+ },
+ moduleFileExtensions: ['ts', 'js', 'json'],
+ verbose: true,
+ testTimeout: 10000,
+ setupFilesAfterEnv: ['/src/__tests__/setup.ts'],
+ extensionsToTreatAsEsm: ['.ts'],
+};
diff --git a/package-lock.json b/package-lock.json
index 30b9a50..4a7cc83 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "flashforge-webui",
- "version": "1.0.0",
+ "version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flashforge-webui",
- "version": "1.0.0",
+ "version": "1.0.2",
"license": "MIT",
"dependencies": {
"@cycjimmy/jsmpeg-player": "^6.1.2",
@@ -21,9 +21,15 @@
"ws": "^8.18.3",
"zod": "^4.0.5"
},
+ "bin": {
+ "flashforge-webui": "dist/index.js"
+ },
"devDependencies": {
+ "@jest/globals": "^30.2.0",
"@types/express": "^4.17.21",
+ "@types/jest": "^30.0.0",
"@types/node": "^20.17.9",
+ "@types/supertest": "^6.0.3",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0",
@@ -31,8 +37,11 @@
"concurrently": "^9.1.2",
"eslint": "^9.16.0",
"globals": "^16.5.0",
+ "jest": "^30.2.0",
"nodemon": "^3.1.11",
"rimraf": "^6.0.1",
+ "supertest": "^7.2.2",
+ "ts-jest": "^29.4.6",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"typescript-eslint": "^8.47.0"
@@ -41,304 +50,566 @@
"node": ">=20.0.0"
}
},
- "node_modules/@babel/generator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "node_modules/@babel/code-frame": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
"engines": {
"node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
}
},
- "node_modules/@babel/parser": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.5"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
},
"engines": {
- "node": ">=6.0.0"
+ "node": ">=6.9.0"
}
},
- "node_modules/@babel/types": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@cycjimmy/jsmpeg-player": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.1.2.tgz",
- "integrity": "sha512-U9DBDe5fxHmbwQww9rFxMLNI2Wlg7DhPzI7AVFpq8GehiUP7+NwuMPXpP4zAd52sgkxtOqOeMjgE5g0ZLnQZ0w==",
- "license": "MIT"
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
}
},
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.9.0"
}
},
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=6.0.0"
}
},
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
- "cpu": [
- "loong64"
- ],
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
}
},
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
- "cpu": [
- "mips64el"
- ],
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
"engines": {
- "node": ">=18"
- }
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
},
- "node_modules/@esbuild/linux-ppc64": {
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@cycjimmy/jsmpeg-player": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.1.2.tgz",
+ "integrity": "sha512-U9DBDe5fxHmbwQww9rFxMLNI2Wlg7DhPzI7AVFpq8GehiUP7+NwuMPXpP4zAd52sgkxtOqOeMjgE5g0ZLnQZ0w==",
+ "license": "MIT"
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
@@ -346,50 +617,50 @@
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "aix"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/linux-riscv64": {
+ "node_modules/@esbuild/android-arm": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
- "riscv64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/linux-s390x": {
+ "node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
- "s390x"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/linux-x64": {
+ "node_modules/@esbuild/android-x64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
@@ -397,16 +668,16 @@
"license": "MIT",
"optional": true,
"os": [
- "linux"
+ "android"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/netbsd-arm64": {
+ "node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
@@ -414,16 +685,16 @@
"license": "MIT",
"optional": true,
"os": [
- "netbsd"
+ "darwin"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/netbsd-x64": {
+ "node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
@@ -431,16 +702,16 @@
"license": "MIT",
"optional": true,
"os": [
- "netbsd"
+ "darwin"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/openbsd-arm64": {
+ "node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
@@ -448,16 +719,16 @@
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
+ "freebsd"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/openbsd-x64": {
+ "node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
@@ -465,47 +736,268 @@
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
+ "freebsd"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/openharmony-arm64": {
+ "node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
- "arm64"
+ "arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "openharmony"
+ "linux"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/sunos-x64": {
+ "node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
- "sunos"
+ "linux"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/win32-arm64": {
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
@@ -843,1763 +1335,3984 @@
"node": "20 || >=22"
}
},
- "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==",
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "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/@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==",
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6.0.0"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "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==",
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
+ "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/@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==",
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">= 8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "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==",
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
"engines": {
- "node": ">= 8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "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==",
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
},
"engines": {
- "node": ">= 8"
+ "node": ">=8"
}
},
- "node_modules/@parallel-7/slicer-meta": {
- "version": "1.1.0-20251121155836",
- "resolved": "https://npm.pkg.github.com/download/@parallel-7/slicer-meta/1.1.0-20251121155836/9338b2ad8e107d4deabeb4ddec5746df51990d66",
- "integrity": "sha512-yLwF0qUCkrTy8RkyXJUM80F2ElYscL2GW7vA1LmGNPpdH1McV/XXbA/wi6ZFLJfVBVoSPGE8sNfxsLfW3YRqfg==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "adm-zip": "^0.5.14",
- "date-fns": "^4.1.0",
- "fast-xml-parser": "^5.2.1"
+ "sprintf-js": "~1.0.2"
}
},
- "node_modules/@types/body-parser": {
- "version": "1.19.6",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
- "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "*"
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
}
},
- "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==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
},
- "node_modules/@types/express": {
- "version": "4.17.25",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
- "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "^1"
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@types/express-serve-static-core": {
- "version": "4.19.7",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
- "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/@types/http-errors": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
- "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
},
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
},
- "node_modules/@types/mime": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
},
- "node_modules/@types/node": {
- "version": "20.19.25",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
- "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
}
},
- "node_modules/@types/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
},
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
},
- "node_modules/@types/send": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
- "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "*"
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@types/serve-static": {
- "version": "1.15.10",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
- "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "<1"
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@types/serve-static/node_modules/@types/send": {
- "version": "0.17.6",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
- "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@types/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/node": "*"
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
- "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/type-utils": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "graphemer": "^1.4.0",
- "ignore": "^7.0.0",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.47.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/parser": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
- "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4"
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
- "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.47.0",
- "@typescript-eslint/types": "^8.47.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
}
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
- "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0"
+ "@sinclair/typebox": "^0.34.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
- "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
},
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
- "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/types": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
- "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
- "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.47.0",
- "@typescript-eslint/tsconfig-utils": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
- "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0"
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
- "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
}
},
- "node_modules/@yao-pkg/pkg": {
- "version": "5.16.1",
- "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-5.16.1.tgz",
- "integrity": "sha512-crUlnNFSReFNFuXDc4f3X2ignkFlc9kmEG7Bp/mJMA1jYyqR0lqjZGLgrSDYTYiNsYud8AzgA3RY1DrMdcUZWg==",
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/generator": "^7.23.0",
- "@babel/parser": "^7.23.0",
- "@babel/types": "^7.23.0",
- "@yao-pkg/pkg-fetch": "3.5.16",
- "into-stream": "^6.0.0",
- "minimist": "^1.2.6",
- "multistream": "^4.1.0",
- "picocolors": "^1.1.0",
- "picomatch": "^4.0.2",
- "prebuild-install": "^7.1.1",
- "resolve": "^1.22.0",
- "stream-meter": "^1.0.4",
- "tinyglobby": "^0.2.9"
- },
- "bin": {
- "pkg": "lib-es5/bin.js"
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
}
},
- "node_modules/@yao-pkg/pkg-fetch": {
- "version": "3.5.16",
- "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.16.tgz",
- "integrity": "sha512-mCnZvZz0/Ylpk4TGyt34pqWJyBGYJM8c3dPoMRV8Knodv2QhcYS4iXb5kB/JNWkrRtCKukGZIKkMLXZ3TQlzPg==",
+ "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",
- "dependencies": {
- "https-proxy-agent": "^5.0.0",
- "node-fetch": "^2.6.6",
- "picocolors": "^1.1.0",
- "progress": "^2.0.3",
- "semver": "^7.3.5",
- "tar-fs": "^2.1.1",
- "yargs": "^16.2.0"
- },
- "bin": {
- "pkg-fetch": "lib-es5/bin.js"
+ "engines": {
+ "node": ">=6.0.0"
}
},
- "node_modules/@yao-pkg/pkg-fetch/node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "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": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
+ "license": "MIT"
},
- "node_modules/@yao-pkg/pkg-fetch/node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@yao-pkg/pkg-fetch/node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
}
},
- "node_modules/@yao-pkg/pkg/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12"
+ "node": "^14.21.3 || >=16"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "url": "https://paulmillr.com/funding/"
}
},
- "node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "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==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">= 8"
}
},
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "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==",
"dev": true,
"license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 8"
}
},
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "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==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/adm-zip": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
- "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
- "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
"engines": {
- "node": ">=12.0"
+ "node": ">= 8"
}
},
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/@parallel-7/slicer-meta": {
+ "version": "1.1.0-20251121155836",
+ "resolved": "https://npm.pkg.github.com/download/@parallel-7/slicer-meta/1.1.0-20251121155836/9338b2ad8e107d4deabeb4ddec5746df51990d66",
+ "integrity": "sha512-yLwF0qUCkrTy8RkyXJUM80F2ElYscL2GW7vA1LmGNPpdH1McV/XXbA/wi6ZFLJfVBVoSPGE8sNfxsLfW3YRqfg==",
"dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
+ "adm-zip": "^0.5.14",
+ "date-fns": "^4.1.0",
+ "fast-xml-parser": "^5.2.1"
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "node_modules/@paralleldrive/cuid2": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
+ "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "@noble/hashes": "^1.1.5"
}
},
- "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==",
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
+ "optional": true,
"engines": {
- "node": ">=8"
+ "node": ">=14"
}
},
- "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==",
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
+ "url": "https://opencollective.com/pkgr"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.48",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
+ "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
"dev": true,
- "license": "Python-2.0"
- },
- "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/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "license": "MIT",
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
+ "type-detect": "4.0.8"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "13.0.5",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
+ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
},
- "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==",
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
}
},
- "node_modules/bl/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
+ "@babel/types": "^7.0.0"
}
},
- "node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.0",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
- "on-finished": "^2.4.1",
- "qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
}
},
- "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==",
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "@babel/types": "^7.28.2"
}
},
- "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==",
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
+ "@types/connect": "*",
+ "@types/node": "*"
}
},
- "node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
"license": "MIT",
"dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
+ "@types/node": "*"
}
},
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
+ "node_modules/@types/cookiejar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
+ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/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==",
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
}
},
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
+ "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "license": "MIT"
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "@types/istanbul-lib-coverage": "*"
}
},
- "node_modules/chalk/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "@types/istanbul-lib-report": "*"
}
},
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "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"
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
}
},
- "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==",
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
+ "license": "MIT"
},
- "node_modules/chownr": {
+ "node_modules/@types/methods": {
"version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
+ "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
+ "license": "MIT"
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/@types/node": {
+ "version": "20.19.25",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
+ "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
+ "undici-types": "~6.21.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==",
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
+ "@types/node": "*"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "node_modules/@types/serve-static": {
+ "version": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
},
- "node_modules/concurrently": {
- "version": "9.2.1",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
- "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
"dev": true,
"license": "MIT",
"dependencies": {
- "chalk": "4.1.2",
- "rxjs": "7.8.2",
- "shell-quote": "1.8.3",
- "supports-color": "8.1.1",
- "tree-kill": "1.2.2",
- "yargs": "17.7.2"
- },
- "bin": {
- "conc": "dist/bin/concurrently.js",
- "concurrently": "dist/bin/concurrently.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ "@types/mime": "^1",
+ "@types/node": "*"
}
},
- "node_modules/content-disposition": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
- "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/superagent": {
+ "version": "8.1.9",
+ "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",
+ "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "dependencies": {
+ "@types/cookiejar": "^2.1.5",
+ "@types/methods": "^1.1.4",
+ "@types/node": "*",
+ "form-data": "^4.0.0"
}
},
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "node_modules/@types/supertest": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz",
+ "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 0.6"
+ "dependencies": {
+ "@types/methods": "^1.1.4",
+ "@types/superagent": "^8.1.0"
}
},
- "node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 0.6"
+ "dependencies": {
+ "@types/node": "*"
}
},
- "node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.6.0"
+ "dependencies": {
+ "@types/yargs-parser": "*"
}
},
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true,
"license": "MIT"
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
+ "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/type-utils": "8.47.0",
+ "@typescript-eslint/utils": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
},
"engines": {
- "node": ">= 8"
- }
- },
- "node_modules/date-fns": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
- "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
- "license": "MIT",
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/kossnocorp"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.47.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
+ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "ms": "^2.1.3"
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
+ "debug": "^4.3.4"
},
"engines": {
- "node": ">=6.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
+ "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "mimic-response": "^3.1.0"
+ "@typescript-eslint/tsconfig-utils": "^8.47.0",
+ "@typescript-eslint/types": "^8.47.0",
+ "debug": "^4.3.4"
},
"engines": {
- "node": ">=10"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/deep-extend": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
- "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
+ "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0"
+ },
"engines": {
- "node": ">=4.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
+ "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
"dev": true,
- "license": "MIT"
- },
- "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": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
+ "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0",
+ "@typescript-eslint/utils": "8.47.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
"engines": {
- "node": ">= 0.8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
+ "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "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==",
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
+ "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
+ "@typescript-eslint/project-service": "8.47.0",
+ "@typescript-eslint/tsconfig-utils": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "license": "MIT"
- },
- "node_modules/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==",
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
+ "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0"
+ },
"engines": {
- "node": ">= 0.8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
}
},
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
+ "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "once": "^1.4.0"
+ "@typescript-eslint/types": "8.47.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "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",
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">= 0.4"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/es-errors": {
+ "node_modules/@ungap/structured-clone": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@yao-pkg/pkg": {
+ "version": "5.16.1",
+ "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-5.16.1.tgz",
+ "integrity": "sha512-crUlnNFSReFNFuXDc4f3X2ignkFlc9kmEG7Bp/mJMA1jYyqR0lqjZGLgrSDYTYiNsYud8AzgA3RY1DrMdcUZWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/generator": "^7.23.0",
+ "@babel/parser": "^7.23.0",
+ "@babel/types": "^7.23.0",
+ "@yao-pkg/pkg-fetch": "3.5.16",
+ "into-stream": "^6.0.0",
+ "minimist": "^1.2.6",
+ "multistream": "^4.1.0",
+ "picocolors": "^1.1.0",
+ "picomatch": "^4.0.2",
+ "prebuild-install": "^7.1.1",
+ "resolve": "^1.22.0",
+ "stream-meter": "^1.0.4",
+ "tinyglobby": "^0.2.9"
+ },
+ "bin": {
+ "pkg": "lib-es5/bin.js"
+ }
+ },
+ "node_modules/@yao-pkg/pkg-fetch": {
+ "version": "3.5.16",
+ "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.16.tgz",
+ "integrity": "sha512-mCnZvZz0/Ylpk4TGyt34pqWJyBGYJM8c3dPoMRV8Knodv2QhcYS4iXb5kB/JNWkrRtCKukGZIKkMLXZ3TQlzPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "https-proxy-agent": "^5.0.0",
+ "node-fetch": "^2.6.6",
+ "picocolors": "^1.1.0",
+ "progress": "^2.0.3",
+ "semver": "^7.3.5",
+ "tar-fs": "^2.1.1",
+ "yargs": "^16.2.0"
+ },
+ "bin": {
+ "pkg-fetch": "lib-es5/bin.js"
+ }
+ },
+ "node_modules/@yao-pkg/pkg-fetch/node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/@yao-pkg/pkg-fetch/node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@yao-pkg/pkg-fetch/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@yao-pkg/pkg/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/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/adm-zip": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
+ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz",
+ "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bl/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001766",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
+ "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
+ "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cookiejar": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.279",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz",
+ "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-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.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
+ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.1",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "dev": true,
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-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==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-xml-parser": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.2.tgz",
+ "integrity": "sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "strnum": "^2.1.0"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">= 0.6"
}
},
- "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==",
+ "node_modules/form-data/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0"
+ "mime-db": "1.52.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">= 0.6"
}
},
- "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==",
+ "node_modules/formidable": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
+ "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
+ "@paralleldrive/cuid2": "^2.2.2",
+ "dezalgo": "^1.0.4",
+ "once": "^1.4.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
- "node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
- "dev": true,
- "hasInstallScript": true,
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
"engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
+ "node": ">= 0.6"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">= 0.8"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "node_modules/from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
+ "hasInstallScript": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=10"
- },
+ "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/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
+ "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": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
- "@eslint/plugin-kit": "^0.4.1",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
+ "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": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
"dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "node_modules/get-tsconfig": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
+ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
+ "license": "MIT"
},
- "node_modules/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "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://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": "*"
+ "node": ">=10.13.0"
}
},
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "node_modules/glob/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "ISC"
+ },
+ "node_modules/glob/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=16 || 14 >=14.18"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.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.10"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
+ "license": "ISC"
},
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
+ "license": "MIT"
},
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "node_modules/gridstack": {
+ "version": "12.3.3",
+ "resolved": "https://registry.npmjs.org/gridstack/-/gridstack-12.3.3.tgz",
+ "integrity": "sha512-Bboi4gj7HXGnx1VFXQNde4Nwi5srdUSuCCnOSszKhFjBs8EtMEWhsKX02BjIKkErq/FjQUkNUbXUYeQaVMQ0jQ==",
+ "funding": [
+ {
+ "type": "paypal",
+ "url": "https://www.paypal.me/alaind831"
+ },
+ {
+ "type": "venmo",
+ "url": "https://www.venmo.com/adumesny"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
}
},
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">=8"
}
},
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "dev": true,
- "license": "(MIT OR WTFPL)",
+ "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": ">=6"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "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": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.0",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
+ "has-symbols": "^1.0.3"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 0.4"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "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/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
- "node": ">=8.6.0"
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "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==",
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "is-glob": "^4.0.1"
+ "agent-base": "6",
+ "debug": "4"
},
"engines": {
"node": ">= 6"
}
},
- "node_modules/fast-json-stable-stringify": {
+ "node_modules/human-signals": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
- "license": "MIT"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
},
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
},
- "node_modules/fast-xml-parser": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.2.tgz",
- "integrity": "sha512-n8v8b6p4Z1sMgqRmqLJm3awW4NX7NkaKPfb3uJIBTSH7Pdvufi3PQ3/lJLQrvxcMYl7JI2jnDO90siPEpD8JBA==",
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
"funding": [
{
"type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
}
],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "strnum": "^2.1.0"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
+ "engines": {
+ "node": ">= 4"
}
},
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
+ "license": "ISC"
},
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flat-cache": "^4.0.0"
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "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==",
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "to-regex-range": "^5.0.1"
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
},
"engines": {
"node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/finalhandler": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
- "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- },
"engines": {
- "node": ">= 0.8"
+ "node": ">=0.8.19"
}
},
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/into-stream": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz",
+ "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
+ "from2": "^2.3.0",
+ "p-is-promise": "^3.0.0"
},
"engines": {
"node": ">=10"
@@ -2608,547 +5321,805 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
"engines": {
- "node": ">=16"
+ "node": ">= 0.10"
}
},
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
- "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"
- }
- ],
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=4.0"
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
},
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
"license": "MIT",
- "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": ">=0.10.0"
}
},
- "node_modules/form-data/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">=8"
}
},
- "node_modules/form-data/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
"engines": {
- "node": ">= 0.6"
+ "node": ">=6"
}
},
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
"engines": {
- "node": ">= 0.6"
+ "node": ">=0.10.0"
}
},
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.8"
+ "node": ">=0.12.0"
}
},
- "node_modules/from2": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
- "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0"
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/fs-constants": {
+ "node_modules/isarray": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"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==",
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">=8"
}
},
- "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/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/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==",
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
- "license": "ISC",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
"engines": {
- "node": "6.* || 8.* || >= 10.*"
+ "node": ">=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==",
+ "node_modules/istanbul-lib-report/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "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"
+ "has-flag": "^4.0.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8"
}
},
- "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",
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=10"
}
},
- "node_modules/get-tsconfig": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
- "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-3-Clause",
"dependencies": {
- "resolve-pkg-maps": "^1.0.0"
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
},
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
- "license": "MIT"
+ "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/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==",
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "is-glob": "^4.0.3"
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
},
"engines": {
- "node": ">=10.13.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
}
},
- "node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
"engines": {
- "node": ">= 0.4"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
}
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/gridstack": {
- "version": "12.3.3",
- "resolved": "https://registry.npmjs.org/gridstack/-/gridstack-12.3.3.tgz",
- "integrity": "sha512-Bboi4gj7HXGnx1VFXQNde4Nwi5srdUSuCCnOSszKhFjBs8EtMEWhsKX02BjIKkErq/FjQUkNUbXUYeQaVMQ0jQ==",
- "funding": [
- {
- "type": "paypal",
- "url": "https://www.paypal.me/alaind831"
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
},
- {
- "type": "venmo",
- "url": "https://www.venmo.com/adumesny"
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
}
- ],
- "license": "MIT"
+ }
},
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "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==",
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "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==",
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "has-symbols": "^1.0.3"
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "function-bind": "^1.1.2"
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
},
"engines": {
- "node": ">= 0.8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
}
},
- "node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "agent-base": "6",
- "debug": "4"
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
},
"engines": {
- "node": ">= 6"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
},
"engines": {
- "node": ">=0.10.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
"engines": {
- "node": ">= 4"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
},
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.8.19"
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
},
- "node_modules/into-stream": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz",
- "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==",
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "from2": "^2.3.0",
- "p-is-promise": "^3.0.0"
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
"engines": {
- "node": ">= 0.10"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "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==",
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "binary-extensions": "^2.0.0"
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
},
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "node_modules/jest-util/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": ">=0.10.0"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "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==",
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "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==",
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-extglob": "^2.1.1"
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
},
"engines": {
- "node": ">=0.10.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.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==",
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
"engines": {
- "node": ">=0.12.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/is-promise": {
+ "node_modules/js-tokens": {
"version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
- "license": "MIT"
- },
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@@ -3182,6 +6153,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3196,6 +6174,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -3206,6 +6197,16 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3220,6 +6221,13 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3236,6 +6244,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3243,12 +6258,55 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lucide": {
- "version": "0.552.0",
- "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.552.0.tgz",
- "integrity": "sha512-f9PSKLsd4TtGRnRnbqZ2IMKQ2tfCA/dwHaZHysmB3LAF8uRi2GB35iy6S/kjcdvglDrueUdpu50ZDBoB21WT2g==",
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide": {
+ "version": "0.552.0",
+ "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.552.0.tgz",
+ "integrity": "sha512-f9PSKLsd4TtGRnRnbqZ2IMKQ2tfCA/dwHaZHysmB3LAF8uRi2GB35iy6S/kjcdvglDrueUdpu50ZDBoB21WT2g==",
+ "license": "ISC"
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
"license": "ISC"
},
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -3279,6 +6337,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3289,6 +6354,16 @@
"node": ">= 8"
}
},
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -3303,6 +6378,19 @@
"node": ">=8.6"
}
},
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
@@ -3328,6 +6416,16 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -3437,6 +6535,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -3453,6 +6567,13 @@
"node": ">= 0.6"
}
},
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-abi": {
"version": "3.85.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
@@ -3487,6 +6608,20 @@
}
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-rtsp-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/node-rtsp-stream/-/node-rtsp-stream-0.0.9.tgz",
@@ -3603,6 +6738,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -3636,6 +6784,22 @@
"wrappy": "1"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3696,6 +6860,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -3716,6 +6890,25 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -3735,6 +6928,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -3809,6 +7012,85 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/prebuild-install": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
@@ -3846,6 +7128,34 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -3910,10 +7220,27 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -4037,6 +7364,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -4097,6 +7431,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -4425,6 +7782,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -4474,15 +7844,76 @@
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
"engines": {
- "node": ">=10"
+ "node": ">=8"
}
},
"node_modules/statuses": {
@@ -4514,6 +7945,20 @@
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -4529,6 +7974,22 @@
"node": ">=8"
}
},
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -4542,6 +8003,40 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -4567,6 +8062,42 @@
],
"license": "MIT"
},
+ "node_modules/superagent": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
+ "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "component-emitter": "^1.3.1",
+ "cookiejar": "^2.1.4",
+ "debug": "^4.3.7",
+ "fast-safe-stringify": "^2.1.1",
+ "form-data": "^4.0.5",
+ "formidable": "^3.5.4",
+ "methods": "^1.1.2",
+ "mime": "2.6.0",
+ "qs": "^6.14.1"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/supertest": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
+ "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie-signature": "^1.2.2",
+ "methods": "^1.1.2",
+ "superagent": "^10.3.0"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -4596,6 +8127,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@@ -4641,6 +8188,67 @@
"node": ">= 6"
}
},
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -4689,6 +8297,13 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4751,6 +8366,72 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -4804,6 +8485,29 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
@@ -4856,6 +8560,20 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -4879,6 +8597,72 @@
"node": ">= 0.8"
}
},
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -4896,6 +8680,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -4905,6 +8704,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -4949,6 +8758,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -4967,12 +8783,45 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
@@ -5004,6 +8853,13 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/package.json b/package.json
index 001f2dc..d36d7bb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "flashforge-webui",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "Standalone WebUI for FlashForge 3D Printers",
"main": "dist/index.js",
"bin": "dist/index.js",
@@ -16,12 +16,12 @@
"build:webui": "tsc --project src/webui/static/tsconfig.json && npm run build:webui:copy",
"build:webui:watch": "tsc --project src/webui/static/tsconfig.json --watch",
"build:webui:copy": "node scripts/copy-webui-assets.js",
- "build:linux": "npm run build && yao-pkg . --targets node20-linux-x64 --output dist/flashforge-webui-linux-x64",
- "build:linux-arm": "npm run build && yao-pkg . --targets node20-linux-arm64 --output dist/flashforge-webui-linux-arm64",
- "build:linux-armv7": "npm run build && yao-pkg . --targets node20.19.5-linux-armv7 --output dist/flashforge-webui-linux-armv7",
- "build:win": "npm run build && yao-pkg . --targets node20-win-x64 --output dist/flashforge-webui-win-x64.exe",
- "build:mac": "npm run build && yao-pkg . --targets node20-macos-x64 --output dist/flashforge-webui-macos-x64",
- "build:mac-arm": "npm run build && yao-pkg . --targets node20-macos-arm64 --output dist/flashforge-webui-macos-arm64",
+ "build:linux": "npm run build && pkg . --targets node20-linux-x64 --output dist/flashforge-webui-linux-x64",
+ "build:linux-arm": "npm run build && pkg . --targets node20-linux-arm64 --output dist/flashforge-webui-linux-arm64",
+ "build:linux-armv7": "npm run build && pkg . --targets node20.19.5-linux-armv7 --output dist/flashforge-webui-linux-armv7",
+ "build:win": "npm run build && pkg . --targets node20-win-x64 --output dist/flashforge-webui-win-x64.exe",
+ "build:mac": "npm run build && pkg . --targets node20-macos-x64 --output dist/flashforge-webui-macos-x64",
+ "build:mac-arm": "npm run build && pkg . --targets node20-macos-arm64 --output dist/flashforge-webui-macos-arm64",
"build:all": "npm run build && npm run build:linux && npm run build:linux-arm && npm run build:linux-armv7 && npm run build:win && npm run build:mac && npm run build:mac-arm",
"build:wrapper": "tsx scripts/platform-build-wrapper.ts",
"build:win:wrapped": "npm run build:wrapper -- --platform win",
@@ -35,7 +35,10 @@
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"type-check": "tsc --noEmit",
- "test": "echo \"Tests not yet implemented\" && exit 0"
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:coverage": "jest --coverage",
+ "test:verbose": "jest --verbose"
},
"keywords": [
"flashforge",
@@ -60,17 +63,23 @@
"zod": "^4.0.5"
},
"devDependencies": {
+ "@jest/globals": "^30.2.0",
"@types/express": "^4.17.21",
+ "@types/jest": "^30.0.0",
"@types/node": "^20.17.9",
+ "@types/supertest": "^6.0.3",
"@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0",
+ "@yao-pkg/pkg": "^5.15.0",
"concurrently": "^9.1.2",
"eslint": "^9.16.0",
"globals": "^16.5.0",
+ "jest": "^30.2.0",
"nodemon": "^3.1.11",
- "@yao-pkg/pkg": "^5.15.0",
"rimraf": "^6.0.1",
+ "supertest": "^7.2.2",
+ "ts-jest": "^29.4.6",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"typescript-eslint": "^8.47.0"
diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts
new file mode 100644
index 0000000..4a9253a
--- /dev/null
+++ b/src/__tests__/setup.ts
@@ -0,0 +1,23 @@
+/**
+ * Jest setup file
+ * Runs before each test file
+ */
+
+import { jest, afterEach } from '@jest/globals';
+
+// Mock console methods to reduce noise in tests
+global.console = {
+ ...console,
+ // Keep error logging for debugging test failures
+ error: jest.fn(),
+ warn: jest.fn(),
+ // Silence info/debug logs in tests
+ log: jest.fn(),
+ info: jest.fn(),
+ debug: jest.fn(),
+};
+
+// Clean up mocks after each test
+afterEach(() => {
+ jest.clearAllMocks();
+});
diff --git a/src/index.ts b/src/index.ts
index 313af24..4e5afe3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -28,6 +28,8 @@ import { getRtspStreamService } from './services/RtspStreamService';
import { initializeSpoolmanIntegrationService } from './services/SpoolmanIntegrationService';
import { getSavedPrinterService } from './services/SavedPrinterService';
import { parseHeadlessArguments, validateHeadlessConfig } from './utils/HeadlessArguments';
+import * as readline from 'readline';
+import { createHardDeadline } from './utils/ShutdownTimeout';
import type { HeadlessConfig, PrinterSpec } from './utils/HeadlessArguments';
import type { PrinterDetails, PrinterClientType } from './types/printer';
import { initializeDataDirectory } from './utils/setup';
@@ -46,6 +48,25 @@ const _cameraProxyService = getCameraProxyService();
let connectedContexts: string[] = [];
let isInitialized = false;
+let isShuttingDown = false;
+
+/**
+ * Shutdown timeout configuration
+ *
+ * Layered timeout strategy:
+ * 1. Per-operation timeouts (disconnect: 5s, webui: 3s)
+ * 2. Hard deadline (10s absolute maximum)
+ *
+ * This prevents hangs from unresponsive printers or stuck HTTP connections
+ */
+const SHUTDOWN_CONFIG = {
+ /** Hard deadline - forces process.exit(1) if exceeded */
+ HARD_DEADLINE_MS: 10000,
+ /** Per-printer disconnect timeout (parallelized, so 3 printers = ~5s total) */
+ DISCONNECT_TIMEOUT_MS: 5000,
+ /** WebUI server graceful close timeout */
+ WEBUI_STOP_TIMEOUT_MS: 3000
+} as const;
/**
* Apply configuration overrides from CLI arguments
@@ -247,7 +268,12 @@ async function initializeCameraProxies(): Promise {
function setupSignalHandlers(): void {
// Handle Ctrl+C (works on all platforms including Windows)
process.on('SIGINT', () => {
+ if (isShuttingDown) {
+ console.log('\n[Shutdown] Force exit (second Ctrl+C)');
+ process.exit(1);
+ }
console.log('\n[Shutdown] Received SIGINT signal (Ctrl+C)');
+ isShuttingDown = true;
void shutdown().then(() => {
process.exit(0);
}).catch((error) => {
@@ -269,7 +295,6 @@ function setupSignalHandlers(): void {
// Windows-specific: Handle process termination
if (process.platform === 'win32') {
- const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
@@ -283,36 +308,69 @@ function setupSignalHandlers(): void {
/**
* Gracefully shutdown the application
+ *
+ * Implements a three-tier timeout strategy:
+ * 1. Hard deadline (10s) - ultimate fallback with process.exit(1)
+ * 2. Parallel disconnects (5s each, concurrent) - one hung printer doesn't block others
+ * 3. WebUI stop (3s) - force-close connections if timeout
+ *
+ * This ensures the application always exits within 10 seconds, even if
+ * printers are unresponsive or HTTP connections are stuck.
*/
async function shutdown(): Promise {
if (!isInitialized) {
return;
}
- console.log('[Shutdown] Stopping services...');
+ const startTime = Date.now();
+ console.log('[Shutdown] Starting graceful shutdown...');
+
+ // Set hard deadline - ultimate fallback to prevent indefinite hangs
+ const hardDeadline = createHardDeadline(SHUTDOWN_CONFIG.HARD_DEADLINE_MS);
try {
- // Stop all polling
+ // Step 1: Stop polling (immediate)
+ console.log('[Shutdown] Step 1/4: Stopping polling...');
pollingCoordinator.stopAllPolling();
- console.log('[Shutdown] Polling stopped');
+ console.log('[Shutdown] ✓ Polling stopped');
- // Disconnect all printers
- for (const contextId of connectedContexts) {
- try {
- await connectionManager.disconnectContext(contextId);
- console.log(`[Shutdown] Disconnected context: ${contextId}`);
- } catch (error) {
- console.error(`[Shutdown] Error disconnecting context ${contextId}:`, error);
- }
+ // Step 2: Parallel disconnects (all printers disconnect concurrently)
+ console.log(`[Shutdown] Step 2/4: Disconnecting ${connectedContexts.length} context(s)...`);
+ if (connectedContexts.length > 0) {
+ const results = await Promise.allSettled(
+ connectedContexts.map(contextId =>
+ connectionManager.disconnectContext(contextId)
+ )
+ );
+
+ const succeeded = results.filter(r => r.status === 'fulfilled').length;
+ const failed = results.filter(r => r.status === 'rejected').length;
+
+ console.log(`[Shutdown] ✓ Disconnect: ${succeeded} succeeded, ${failed} failed`);
+
+ // Log individual failures for debugging
+ results.forEach((result, index) => {
+ if (result.status === 'rejected') {
+ console.warn(`[Shutdown] Context ${connectedContexts[index]} failed:`, result.reason);
+ }
+ });
+ } else {
+ console.log('[Shutdown] ✓ No contexts to disconnect');
}
- // Stop WebUI
- await webUIManager.stop();
- console.log('[Shutdown] WebUI server stopped');
+ // Step 3: Stop WebUI (with timeout and force-close fallback)
+ console.log('[Shutdown] Step 3/4: Stopping WebUI...');
+ await webUIManager.stop(SHUTDOWN_CONFIG.WEBUI_STOP_TIMEOUT_MS);
+ console.log('[Shutdown] ✓ WebUI stopped');
+
+ // Step 4: Complete (cancel hard deadline)
+ clearTimeout(hardDeadline);
+ const duration = Date.now() - startTime;
+ console.log(`[Shutdown] ✓ Complete (${duration}ms)`);
- console.log('[Shutdown] Graceful shutdown complete');
} catch (error) {
- console.error('[Shutdown] Error during shutdown:', error);
+ console.error('[Shutdown] Error:', error);
+ // Hard deadline will still fire if we exceed max time
}
}
diff --git a/src/managers/ConfigManager.test.ts b/src/managers/ConfigManager.test.ts
new file mode 100644
index 0000000..2fa2a35
--- /dev/null
+++ b/src/managers/ConfigManager.test.ts
@@ -0,0 +1,276 @@
+/**
+ * @fileoverview Tests for ConfigManager
+ * Tests configuration loading, saving, validation, and event emission
+ */
+
+import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
+import * as fs from 'fs';
+import { EventEmitter } from 'events';
+import { ConfigManager, getConfigManager } from './ConfigManager';
+
+// Mock fs and path
+jest.mock('fs');
+jest.mock('path');
+
+describe('ConfigManager', () => {
+ let configManager: ConfigManager;
+
+ beforeEach(() => {
+ // Setup mocks
+
+ // Mock fs.existsSync to return true for config directory
+ jest.spyOn(fs, 'existsSync').mockReturnValue(true);
+
+ // Mock fs.readFileSync to return default config
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({
+ WebUIEnabled: true,
+ WebUIPort: 3000,
+ WebUIPassword: 'testpass',
+ WebUIPasswordRequired: true,
+ SpoolmanEnabled: false,
+ SpoolmanServerUrl: '',
+ CameraProxyPort: 8181
+ }));
+
+ // Mock fs.mkdirSync
+ jest.spyOn(fs, 'mkdirSync').mockImplementation(() => undefined as any);
+
+ // Mock fs.writeFileSync
+ jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
+
+ // Reset singleton
+ (ConfigManager as any).instance = null;
+ configManager = getConfigManager();
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('Singleton Pattern', () => {
+ it('should return the same instance from getConfigManager', () => {
+ const instance1 = getConfigManager();
+ const instance2 = getConfigManager();
+
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should extend EventEmitter', () => {
+ expect(configManager).toBeInstanceOf(EventEmitter);
+ });
+ });
+
+ describe('Configuration Loading', () => {
+ it('should load configuration from file', () => {
+ const config = configManager.getConfig();
+
+ expect(config).toBeDefined();
+ expect(typeof config).toBe('object');
+ });
+
+ it('should have all required configuration keys', () => {
+ const config = configManager.getConfig();
+
+ expect(config).toHaveProperty('WebUIEnabled');
+ expect(config).toHaveProperty('WebUIPort');
+ expect(config).toHaveProperty('WebUIPassword');
+ expect(config).toHaveProperty('WebUIPasswordRequired');
+ expect(config).toHaveProperty('SpoolmanEnabled');
+ expect(config).toHaveProperty('SpoolmanServerUrl');
+ expect(config).toHaveProperty('CameraProxyPort');
+ });
+
+ it('should return correct types for configuration values', () => {
+ const config = configManager.getConfig();
+
+ expect(typeof config.WebUIEnabled).toBe('boolean');
+ expect(typeof config.WebUIPort).toBe('number');
+ expect(typeof config.WebUIPassword).toBe('string');
+ expect(typeof config.WebUIPasswordRequired).toBe('boolean');
+ expect(typeof config.SpoolmanEnabled).toBe('boolean');
+ expect(typeof config.SpoolmanServerUrl).toBe('string');
+ expect(typeof config.CameraProxyPort).toBe('number');
+ });
+ });
+
+ describe('Configuration Getters', () => {
+ it('should get single configuration value', () => {
+ const port = configManager.get('WebUIPort');
+
+ expect(port).toBe(3000);
+ });
+
+ it('should return undefined for non-existent key', () => {
+ const value = configManager.get('NonExistentKey' as any);
+
+ expect(value).toBeUndefined();
+ });
+
+ it('should get entire configuration', () => {
+ const config = configManager.getConfig();
+
+ expect(Object.keys(config).length).toBeGreaterThan(0);
+ });
+ });
+
+ describe('Configuration Updates', () => {
+ it('should emit event when configuration is updated', (done) => {
+ configManager.once('configUpdated', (event) => {
+ expect(event).toHaveProperty('changedKeys');
+ expect(Array.isArray(event.changedKeys)).toBe(true);
+ done();
+ });
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+ });
+
+ it('should emit event with list of changed keys', (done) => {
+ configManager.once('configUpdated', (event) => {
+ expect(event.changedKeys).toContain('WebUIPort');
+ done();
+ });
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+ });
+
+ it('should update configuration value', (done) => {
+ configManager.once('configUpdated', () => {
+ const newPort = configManager.get('WebUIPort');
+ expect(newPort).toBe(3001);
+ done();
+ });
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+ });
+
+ it('should update multiple configuration values', (done) => {
+ configManager.once('configUpdated', (event) => {
+ expect(event.changedKeys).toContain('WebUIPort');
+ expect(event.changedKeys).toContain('SpoolmanEnabled');
+ done();
+ });
+
+ configManager.updateConfig({
+ WebUIPort: 3001,
+ SpoolmanEnabled: true
+ });
+ });
+ });
+
+ describe('Configuration Validation', () => {
+ it('should have valid port numbers', () => {
+ const config = configManager.getConfig();
+
+ expect(config.WebUIPort).toBeGreaterThanOrEqual(1);
+ expect(config.WebUIPort).toBeLessThanOrEqual(65535);
+ expect(config.CameraProxyPort).toBeGreaterThanOrEqual(1);
+ expect(config.CameraProxyPort).toBeLessThanOrEqual(65535);
+ });
+
+ it('should have valid URL format for SpoolmanServerUrl', () => {
+ const config = configManager.getConfig();
+
+ // Empty string is valid (Spoolman disabled)
+ if (config.SpoolmanServerUrl) {
+ expect(config.SpoolmanServerUrl).toMatch(/^https?:\/\//);
+ }
+ });
+
+ it('should have non-empty password when password required', () => {
+ const config = configManager.getConfig();
+
+ if (config.WebUIPasswordRequired) {
+ expect(config.WebUIPassword.length).toBeGreaterThan(0);
+ }
+ });
+ });
+
+ describe('Default Values', () => {
+ it('should use sensible defaults for WebUI configuration', () => {
+ const config = configManager.getConfig();
+
+ expect(config.WebUIEnabled).toBeDefined();
+ expect(config.WebUIPort).toBeDefined();
+ expect(config.WebUIPassword).toBeDefined();
+ expect(config.WebUIPasswordRequired).toBeDefined();
+ });
+
+ it('should use safe defaults for security-sensitive settings', () => {
+ const config = configManager.getConfig();
+
+ // Password should be required by default for security
+ expect(config.WebUIPasswordRequired).toBe(true);
+ });
+ });
+
+ describe('File System Operations', () => {
+ it('should create data directory if it does not exist', () => {
+ (fs.existsSync as jest.Mock).mockReturnValueOnce(false);
+
+ (ConfigManager as any).instance = null;
+ getConfigManager();
+
+ expect(fs.mkdirSync).toHaveBeenCalled();
+ });
+
+ it('should write configuration to file', () => {
+ configManager.updateConfig({ WebUIPort: 3001 });
+
+ // The actual ConfigManager schedules saves, so we just verify the method exists
+ expect(typeof configManager.forceSave).toBe('function');
+ });
+ });
+
+ describe('Event Emission', () => {
+ it('should allow multiple listeners for configUpdated', () => {
+ const listener1 = jest.fn();
+ const listener2 = jest.fn();
+
+ configManager.on('configUpdated', listener1);
+ configManager.on('configUpdated', listener2);
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+
+ expect(listener1).toHaveBeenCalled();
+ expect(listener2).toHaveBeenCalled();
+ });
+
+ it('should pass changed keys in event data', (done) => {
+ configManager.on('configUpdated', (event) => {
+ expect(event).toHaveProperty('changedKeys');
+ expect(Array.isArray(event.changedKeys)).toBe(true);
+ done();
+ });
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle update with no changes', () => {
+ const config = configManager.getConfig();
+
+ // ConfigManager does NOT emit an event if there are no changes
+ // So we just verify it doesn't crash
+ expect(() => {
+ configManager.updateConfig({ WebUIPort: config.WebUIPort });
+ }).not.toThrow();
+ });
+
+ it('should handle multiple rapid updates', (done) => {
+ let updates = 0;
+
+ configManager.on('configUpdated', () => {
+ updates++;
+ if (updates === 3) {
+ expect(updates).toBe(3);
+ done();
+ }
+ });
+
+ configManager.updateConfig({ WebUIPort: 3001 });
+ configManager.updateConfig({ WebUIPort: 3002 });
+ configManager.updateConfig({ WebUIPort: 3003 });
+ });
+ });
+});
diff --git a/src/managers/ConnectionFlowManager.ts b/src/managers/ConnectionFlowManager.ts
index 706bf94..b0d36f2 100644
--- a/src/managers/ConnectionFlowManager.ts
+++ b/src/managers/ConnectionFlowManager.ts
@@ -33,6 +33,7 @@ import { getLoadingManager } from './LoadingManager';
import { getPrinterBackendManager } from './PrinterBackendManager';
import { getPrinterContextManager } from './PrinterContextManager';
import { getPrinterDiscoveryService } from '../services/PrinterDiscoveryService';
+import { withTimeout, TimeoutError } from '../utils/ShutdownTimeout';
import { getThumbnailRequestQueue } from '../services/ThumbnailRequestQueue';
import { getSavedPrinterService } from '../services/SavedPrinterService';
import { getAutoConnectService } from '../services/AutoConnectService';
@@ -451,7 +452,7 @@ export class ConnectionFlowManager extends EventEmitter {
}
/** Disconnect a specific printer context with proper cleanup */
- public async disconnectContext(contextId: string): Promise {
+ public async disconnectContext(contextId: string, timeoutMs = 5000): Promise {
const context = this.contextManager.getContext(contextId);
if (!context) {
console.warn(`Cannot disconnect - context ${contextId} not found`);
@@ -461,37 +462,50 @@ export class ConnectionFlowManager extends EventEmitter {
const currentDetails = context.printerDetails;
try {
- console.log(`Starting disconnect sequence for context ${contextId}...`);
-
- // Stop polling first
- this.emit('pre-disconnect', contextId);
- await new Promise(resolve => setTimeout(resolve, 100));
+ await withTimeout(
+ (async () => {
+ console.log(`Starting disconnect sequence for context ${contextId}...`);
+
+ // Stop polling first
+ this.emit('pre-disconnect', contextId);
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Get clients for disposal from connection state
+ const primaryClient = this.connectionStateManager.getPrimaryClient(contextId);
+ const secondaryClient = this.connectionStateManager.getSecondaryClient(contextId);
+
+ // Dispose backend for this context
+ await this.backendManager.disposeContext(contextId);
+
+ // Dispose clients through connection service (handles logout)
+ await this.connectionService.disposeClients(
+ primaryClient,
+ secondaryClient,
+ currentDetails?.ClientType
+ );
- // Get clients for disposal from connection state
- const primaryClient = this.connectionStateManager.getPrimaryClient(contextId);
- const secondaryClient = this.connectionStateManager.getSecondaryClient(contextId);
+ // Update connection state
+ this.connectionStateManager.setDisconnected(contextId);
- // Dispose backend for this context
- await this.backendManager.disposeContext(contextId);
+ // Remove context from manager
+ this.contextManager.removeContext(contextId);
- // Dispose clients through connection service (handles logout)
- await this.connectionService.disposeClients(
- primaryClient,
- secondaryClient,
- currentDetails?.ClientType
+ // Emit disconnected event
+ this.emit('disconnected', currentDetails?.Name);
+ })(),
+ { timeoutMs, operation: `disconnectContext(${contextId})` }
);
-
- // Update connection state
- this.connectionStateManager.setDisconnected(contextId);
-
- // Remove context from manager
- this.contextManager.removeContext(contextId);
-
- // Emit disconnected event
- this.emit('disconnected', currentDetails?.Name);
-
} catch (error) {
- console.error(`Error during disconnect for context ${contextId}:`, error);
+ if (error instanceof TimeoutError) {
+ console.error(`[Shutdown] Context ${contextId} timed out, forcing cleanup`);
+ // Force cleanup on timeout
+ this.contextManager.removeContext(contextId);
+ this.connectionStateManager.setDisconnected(contextId);
+ } else {
+ console.error(`Error during disconnect for context ${contextId}:`, error);
+ }
+ // Re-throw to ensure Promise.allSettled sees this as a rejection
+ throw error;
}
}
diff --git a/src/services/EnvironmentService.test.ts b/src/services/EnvironmentService.test.ts
new file mode 100644
index 0000000..2b53d99
--- /dev/null
+++ b/src/services/EnvironmentService.test.ts
@@ -0,0 +1,261 @@
+/**
+ * @fileoverview Tests for EnvironmentService
+ * Tests environment detection, path resolution, and static file serving paths
+ */
+
+import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
+import * as path from 'path';
+import { EnvironmentService, getEnvironmentService } from './EnvironmentService';
+
+// Mock process.cwd
+const originalCwd = process.cwd;
+const mockEnv = { ...process.env };
+
+describe('EnvironmentService', () => {
+ let service: EnvironmentService;
+
+ beforeEach(() => {
+ // Reset environment
+ process.env = { ...mockEnv };
+ service = new EnvironmentService();
+ });
+
+ afterEach(() => {
+ // Restore original values
+ process.cwd = originalCwd;
+ });
+
+ describe('Package Detection', () => {
+ it('should detect packaged environment via PKG_EXECPATH', () => {
+ process.env.PKG_EXECPATH = '/some/path';
+ const packagedService = new EnvironmentService();
+ expect(packagedService.isPackaged()).toBe(true);
+ });
+
+ it('should detect packaged environment via __dirname snapshot path (Unix)', () => {
+ // Mock __dirname to include snapshot
+ jest.spyOn(process, 'cwd').mockReturnValue('/app/dist');
+
+ // Create a service instance - detection happens in constructor
+ const testService = new EnvironmentService();
+
+ // In normal testing, __dirname won't have /snapshot/ unless we're actually in pkg
+ // So we test the other two detection methods
+ expect(testService).toBeDefined();
+ });
+
+ it('should detect packaged environment via __dirname snapshot path (Windows)', () => {
+ // Similar test for Windows paths
+ const testService = new EnvironmentService();
+ expect(testService).toBeDefined();
+ });
+
+ it('should detect packaged environment via process.pkg', () => {
+ // Mock process.pkg
+ (process as any).pkg = { entrypoint: '/test' };
+
+ const packagedService = new EnvironmentService();
+ expect(packagedService.isPackaged()).toBe(true);
+
+ // Cleanup
+ (process as any).pkg = undefined;
+ });
+
+ it('should not detect packaged environment when no indicators present', () => {
+ // Ensure no pkg indicators
+ delete process.env.PKG_EXECPATH;
+ (process as any).pkg = undefined;
+
+ const devService = new EnvironmentService();
+ // In normal test environment, this should be false
+ expect(devService.isPackaged()).toBe(false);
+ });
+ });
+
+ describe('Environment State', () => {
+ it('should always return false for isElectron in standalone mode', () => {
+ expect(service.isElectron()).toBe(false);
+ });
+
+ it('should return true for isProduction when NODE_ENV is production', () => {
+ process.env.NODE_ENV = 'production';
+ const prodService = new EnvironmentService();
+ expect(prodService.isProduction()).toBe(true);
+ });
+
+ it('should return true for isProduction when packaged', () => {
+ (process as any).pkg = { entrypoint: '/test' };
+ const packagedService = new EnvironmentService();
+ expect(packagedService.isProduction()).toBe(true);
+ (process as any).pkg = undefined;
+ });
+
+ it('should return false for isProduction when in development', () => {
+ process.env.NODE_ENV = 'development';
+ delete process.env.PKG_EXECPATH;
+
+ const devService = new EnvironmentService();
+ expect(devService.isProduction()).toBe(false);
+ });
+
+ it('should return correct isDevelopment state', () => {
+ process.env.NODE_ENV = 'development';
+ const devService = new EnvironmentService();
+
+ expect(devService.isDevelopment()).toBe(true);
+ expect(devService.isProduction()).toBe(false);
+ });
+ });
+
+ describe('Path Resolution', () => {
+ it('should return correct data path', () => {
+ const mockCwd = '/mock/app/directory';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ const testService = new EnvironmentService();
+ expect(testService.getDataPath()).toBe(path.join(mockCwd, 'data'));
+ });
+
+ it('should return correct logs path', () => {
+ const mockCwd = '/mock/app/directory';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ const testService = new EnvironmentService();
+ expect(testService.getLogsPath()).toBe(path.join(mockCwd, 'data', 'logs'));
+ });
+
+ it('should return correct app root path', () => {
+ const mockCwd = '/mock/app/directory';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ const testService = new EnvironmentService();
+ expect(testService.getAppRootPath()).toBe(mockCwd);
+ });
+
+ it('should return development static path when not packaged', () => {
+ const mockCwd = '/mock/app';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ const devService = new EnvironmentService();
+ const staticPath = devService.getWebUIStaticPath();
+
+ expect(staticPath).toBe(path.join(mockCwd, 'dist/webui/static'));
+ });
+
+ it('should warn when development static path does not exist', () => {
+ const mockCwd = '/mock/app';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ // This test verifies the warning logic exists
+ // In real scenarios, fs.existsSync would check the path
+ const devService = new EnvironmentService();
+ const staticPath = devService.getWebUIStaticPath();
+
+ // Should still return the path even if it doesn't exist
+ expect(staticPath).toBe(path.join(mockCwd, 'dist/webui/static'));
+ });
+
+ it('should return packaged static path when packaged', () => {
+ // Simulate packaged environment
+ (process as any).pkg = { entrypoint: '/test' };
+
+ const packagedService = new EnvironmentService();
+ const staticPath = packagedService.getWebUIStaticPath();
+
+ // In packaged mode, should use __dirname and contain webui/static
+ expect(staticPath).toBeTruthy();
+ expect(typeof staticPath).toBe('string');
+
+ (process as any).pkg = undefined;
+ });
+ });
+
+ describe('Environment Info', () => {
+ it('should return comprehensive environment info', () => {
+ const mockCwd = '/mock/app';
+ jest.spyOn(process, 'cwd').mockReturnValue(mockCwd);
+
+ const testService = new EnvironmentService();
+ const envInfo = testService.getEnvironmentInfo();
+
+ expect(envInfo).toHaveProperty('isPackaged');
+ expect(envInfo).toHaveProperty('isProduction');
+ expect(envInfo).toHaveProperty('isDevelopment');
+ expect(envInfo).toHaveProperty('dirname');
+ expect(envInfo).toHaveProperty('cwd');
+ expect(envInfo).toHaveProperty('staticPath');
+ expect(envInfo).toHaveProperty('dataPath');
+
+ expect(envInfo.cwd).toBe(mockCwd);
+ expect(typeof envInfo.isPackaged).toBe('boolean');
+ expect(typeof envInfo.isProduction).toBe('boolean');
+ expect(typeof envInfo.isDevelopment).toBe('boolean');
+ expect(typeof envInfo.dirname).toBe('string');
+ expect(typeof envInfo.staticPath).toBe('string');
+ expect(typeof envInfo.dataPath).toBe('string');
+ });
+
+ it('should show consistent state between isProduction and isDevelopment', () => {
+ const testService = new EnvironmentService();
+ const envInfo = testService.getEnvironmentInfo();
+
+ expect(envInfo.isProduction).toBe(!envInfo.isDevelopment);
+ });
+ });
+
+ describe('Singleton Pattern', () => {
+ it('should return the same instance from getEnvironmentService', () => {
+ const instance1 = getEnvironmentService();
+ const instance2 = getEnvironmentService();
+
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should maintain singleton across multiple calls', () => {
+ const instances = [
+ getEnvironmentService(),
+ getEnvironmentService(),
+ getEnvironmentService()
+ ];
+
+ expect(instances[0]).toBe(instances[1]);
+ expect(instances[1]).toBe(instances[2]);
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle NODE_ENV unset gracefully', () => {
+ delete process.env.NODE_ENV;
+ delete process.env.PKG_EXECPATH;
+
+ const testService = new EnvironmentService();
+ expect(testService.isProduction()).toBe(false);
+ expect(testService.isDevelopment()).toBe(true);
+ });
+
+ it('should handle various NODE_ENV values', () => {
+ const envValues = ['production', 'development', 'test', 'staging'];
+
+ envValues.forEach(envValue => {
+ process.env.NODE_ENV = envValue;
+ const testService = new EnvironmentService();
+
+ if (envValue === 'production') {
+ expect(testService.isProduction()).toBe(true);
+ } else {
+ expect(testService.isProduction()).toBe(false);
+ }
+ });
+ });
+
+ it('should prioritize packaged detection over NODE_ENV', () => {
+ process.env.NODE_ENV = 'development';
+ (process as any).pkg = { entrypoint: '/test' };
+
+ const testService = new EnvironmentService();
+ expect(testService.isProduction()).toBe(true); // Packaged takes precedence
+
+ (process as any).pkg = undefined;
+ });
+ });
+});
diff --git a/src/services/EnvironmentService.ts b/src/services/EnvironmentService.ts
index 8290cc5..a16a0db 100644
--- a/src/services/EnvironmentService.ts
+++ b/src/services/EnvironmentService.ts
@@ -6,12 +6,53 @@
*/
import * as path from 'path';
+import * as fs from 'fs';
/**
* Environment service for determining runtime environment and paths
* Standalone Node.js implementation
*/
export class EnvironmentService {
+ private readonly _isPackaged: boolean;
+
+ constructor() {
+ // Detect if running as a pkg-bundled binary
+ // In pkg binaries, __dirname points to a snapshot filesystem path
+ // Also check for the PKG_EXECPATH environment variable which pkg sets
+ this._isPackaged = this.detectPackagedEnvironment();
+ }
+
+ /**
+ * Detect if running in a packaged (pkg) environment
+ * Uses multiple detection methods for reliability
+ */
+ private detectPackagedEnvironment(): boolean {
+ // Method 1: Check for PKG_EXECPATH environment variable (set by pkg)
+ if (process.env.PKG_EXECPATH) {
+ return true;
+ }
+
+ // Method 2: Check if __dirname contains snapshot path (pkg uses /snapshot/)
+ if (__dirname.includes('/snapshot/') || __dirname.includes('\\snapshot\\')) {
+ return true;
+ }
+
+ // Method 3: Check if running from a binary (process.pkg exists in pkg binaries)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((process as any).pkg) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if running in a packaged binary (pkg)
+ */
+ public isPackaged(): boolean {
+ return this._isPackaged;
+ }
+
/**
* Check if running in Electron
* Always returns false in standalone implementation
@@ -22,9 +63,10 @@ export class EnvironmentService {
/**
* Check if running in production mode
+ * Returns true if packaged or NODE_ENV is 'production'
*/
public isProduction(): boolean {
- return process.env.NODE_ENV === 'production';
+ return this._isPackaged || process.env.NODE_ENV === 'production';
}
/**
@@ -44,16 +86,27 @@ export class EnvironmentService {
/**
* Get the WebUI static files path
- * In production: relative to compiled dist/
- * In development: relative to source dist/
+ * In packaged binaries: relative to __dirname (embedded in pkg snapshot)
+ * In development: relative to process.cwd()/dist/
*/
public getWebUIStaticPath(): string {
- if (this.isProduction()) {
- // In production, static files are in dist/webui/static relative to the compiled code
- return path.join(__dirname, '../webui/static');
+ if (this._isPackaged) {
+ // In pkg binaries, static files are embedded and accessible via __dirname
+ // __dirname points to the snapshot filesystem where assets are bundled
+ const pkgStaticPath = path.join(__dirname, '../webui/static');
+ return pkgStaticPath;
}
- // In development, static files are in dist/webui/static from project root
- return path.join(process.cwd(), 'dist/webui/static');
+
+ // In development or running via node directly, use process.cwd()
+ const devStaticPath = path.join(process.cwd(), 'dist/webui/static');
+
+ // Verify the path exists for better error messages
+ if (!fs.existsSync(devStaticPath)) {
+ console.warn(`[EnvironmentService] Static path not found: ${devStaticPath}`);
+ console.warn('[EnvironmentService] Did you run "npm run build" first?');
+ }
+
+ return devStaticPath;
}
/**
@@ -69,6 +122,29 @@ export class EnvironmentService {
public getLogsPath(): string {
return path.join(this.getDataPath(), 'logs');
}
+
+ /**
+ * Get environment info for debugging
+ */
+ public getEnvironmentInfo(): {
+ isPackaged: boolean;
+ isProduction: boolean;
+ isDevelopment: boolean;
+ dirname: string;
+ cwd: string;
+ staticPath: string;
+ dataPath: string;
+ } {
+ return {
+ isPackaged: this._isPackaged,
+ isProduction: this.isProduction(),
+ isDevelopment: this.isDevelopment(),
+ dirname: __dirname,
+ cwd: process.cwd(),
+ staticPath: this.getWebUIStaticPath(),
+ dataPath: this.getDataPath()
+ };
+ }
}
// Singleton instance
diff --git a/src/utils/ShutdownTimeout.ts b/src/utils/ShutdownTimeout.ts
new file mode 100644
index 0000000..aa067af
--- /dev/null
+++ b/src/utils/ShutdownTimeout.ts
@@ -0,0 +1,106 @@
+/**
+ * @fileoverview Timeout utilities for graceful shutdown operations.
+ *
+ * Provides timeout wrappers and deadline enforcement for async operations
+ * during application shutdown. Prevents indefinite hangs from unresponsive
+ * printers or stuck HTTP connections.
+ *
+ * Key exports:
+ * - TimeoutError: Custom error class for timeout failures
+ * - withTimeout(): Promise wrapper with timeout enforcement
+ * - createHardDeadline(): Sets absolute maximum shutdown time with process.exit(1)
+ */
+
+/**
+ * Custom error thrown when an operation exceeds its timeout
+ */
+export class TimeoutError extends Error {
+ constructor(operation: string, timeoutMs: number) {
+ super(`Timeout: ${operation} exceeded ${timeoutMs}ms`);
+ this.name = 'TimeoutError';
+ }
+}
+
+/**
+ * Wrap a promise with timeout enforcement
+ *
+ * Races the provided promise against a timeout. If the timeout fires first,
+ * the promise is rejected with TimeoutError. Properly cleans up timeout
+ * handle to prevent memory leaks.
+ *
+ * @template T - Promise result type
+ * @param promise - The promise to wrap with timeout
+ * @param options - Timeout configuration
+ * @returns Promise that rejects on timeout
+ *
+ * @example
+ * ```typescript
+ * await withTimeout(
+ * disconnectContext(contextId),
+ * { timeoutMs: 5000, operation: 'disconnectContext' }
+ * );
+ * ```
+ */
+export async function withTimeout(
+ promise: Promise,
+ options: { timeoutMs: number; operation: string; silent?: boolean }
+): Promise {
+ const { timeoutMs, operation, silent = false } = options;
+
+ let timeoutHandle: NodeJS.Timeout | undefined;
+
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutHandle = setTimeout(() => {
+ if (!silent) {
+ console.warn(`[Shutdown] Timeout: ${operation} (${timeoutMs}ms)`);
+ }
+ reject(new TimeoutError(operation, timeoutMs));
+ }, timeoutMs);
+ });
+
+ try {
+ const result = await Promise.race([promise, timeoutPromise]);
+
+ // Clear timeout if promise won the race
+ if (timeoutHandle) {
+ clearTimeout(timeoutHandle);
+ }
+
+ return result;
+ } catch (error) {
+ // Ensure timeout is cleared even on error
+ if (timeoutHandle) {
+ clearTimeout(timeoutHandle);
+ }
+ throw error;
+ }
+}
+
+/**
+ * Create a hard deadline that forces process termination
+ *
+ * Sets a timeout that calls process.exit(1) when elapsed. This is the
+ * ultimate fallback to prevent the application from hanging indefinitely
+ * during shutdown. Returns the timeout handle so the deadline can be
+ * cleared if shutdown completes successfully.
+ *
+ * @param timeoutMs - Deadline duration in milliseconds
+ * @returns NodeJS.Timeout handle for deadline cancellation
+ *
+ * @example
+ * ```typescript
+ * const deadline = createHardDeadline(10000);
+ * try {
+ * await shutdown();
+ * clearTimeout(deadline); // Shutdown succeeded, cancel deadline
+ * } catch (error) {
+ * // Error logged, deadline will fire if exceeded
+ * }
+ * ```
+ */
+export function createHardDeadline(timeoutMs: number): NodeJS.Timeout {
+ return setTimeout(() => {
+ console.error(`[Shutdown] HARD DEADLINE (${timeoutMs}ms) exceeded - forcing exit`);
+ process.exit(1);
+ }, timeoutMs);
+}
diff --git a/src/utils/error.utils.test.ts b/src/utils/error.utils.test.ts
new file mode 100644
index 0000000..35ca7ae
--- /dev/null
+++ b/src/utils/error.utils.test.ts
@@ -0,0 +1,514 @@
+/**
+ * @fileoverview Tests for error utilities
+ * Tests AppError class, error factory functions, and error handling utilities
+ */
+
+import { describe, it, expect, jest } from '@jest/globals';
+import { ZodError } from 'zod';
+import {
+ AppError,
+ ErrorCode,
+ fromZodError,
+ networkError,
+ timeoutError,
+ printerError,
+ backendError,
+ fileError,
+ isAppError,
+ toAppError,
+ withErrorHandling,
+ createErrorResult,
+ logError
+} from './error.utils';
+
+describe('ErrorCode', () => {
+ it('should have all expected error codes', () => {
+ // General errors
+ expect(ErrorCode.UNKNOWN).toBe('UNKNOWN');
+ expect(ErrorCode.VALIDATION).toBe('VALIDATION');
+ expect(ErrorCode.NETWORK).toBe('NETWORK');
+ expect(ErrorCode.TIMEOUT).toBe('TIMEOUT');
+
+ // Printer errors
+ expect(ErrorCode.PRINTER_NOT_CONNECTED).toBe('PRINTER_NOT_CONNECTED');
+ expect(ErrorCode.PRINTER_BUSY).toBe('PRINTER_BUSY');
+ expect(ErrorCode.PRINTER_ERROR).toBe('PRINTER_ERROR');
+ expect(ErrorCode.PRINTER_COMMUNICATION).toBe('PRINTER_COMMUNICATION');
+
+ // Backend errors
+ expect(ErrorCode.BACKEND_NOT_INITIALIZED).toBe('BACKEND_NOT_INITIALIZED');
+ expect(ErrorCode.BACKEND_OPERATION_FAILED).toBe('BACKEND_OPERATION_FAILED');
+ expect(ErrorCode.BACKEND_UNSUPPORTED).toBe('BACKEND_UNSUPPORTED');
+
+ // File errors
+ expect(ErrorCode.FILE_NOT_FOUND).toBe('FILE_NOT_FOUND');
+ expect(ErrorCode.FILE_TOO_LARGE).toBe('FILE_TOO_LARGE');
+ expect(ErrorCode.FILE_INVALID_FORMAT).toBe('FILE_INVALID_FORMAT');
+ expect(ErrorCode.FILE_UPLOAD_FAILED).toBe('FILE_UPLOAD_FAILED');
+
+ // Configuration errors
+ expect(ErrorCode.CONFIG_INVALID).toBe('CONFIG_INVALID');
+ expect(ErrorCode.CONFIG_SAVE_FAILED).toBe('CONFIG_SAVE_FAILED');
+ expect(ErrorCode.CONFIG_LOAD_FAILED).toBe('CONFIG_LOAD_FAILED');
+
+ // IPC errors
+ expect(ErrorCode.IPC_CHANNEL_INVALID).toBe('IPC_CHANNEL_INVALID');
+ expect(ErrorCode.IPC_TIMEOUT).toBe('IPC_TIMEOUT');
+ expect(ErrorCode.IPC_HANDLER_NOT_FOUND).toBe('IPC_HANDLER_NOT_FOUND');
+ });
+});
+
+describe('AppError', () => {
+ it('should create error with message and code', () => {
+ const error = new AppError('Test error', ErrorCode.NETWORK);
+
+ expect(error.message).toBe('Test error');
+ expect(error.code).toBe(ErrorCode.NETWORK);
+ expect(error.name).toBe('AppError');
+ });
+
+ it('should create error with context', () => {
+ const context = { port: 3000, host: 'localhost' };
+ const error = new AppError('Test error', ErrorCode.NETWORK, context);
+
+ expect(error.context).toEqual(context);
+ expect(error.context?.port).toBe(3000);
+ expect(error.context?.host).toBe('localhost');
+ });
+
+ it('should create error with original error', () => {
+ const originalError = new Error('Original error');
+ const error = new AppError('Wrapped error', ErrorCode.UNKNOWN, undefined, originalError);
+
+ expect(error.originalError).toBe(originalError);
+ expect(error.originalError?.message).toBe('Original error');
+ });
+
+ it('should have timestamp', () => {
+ const before = new Date();
+ const error = new AppError('Test error');
+ const after = new Date();
+
+ expect(error.timestamp).toBeInstanceOf(Date);
+ expect(error.timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
+ expect(error.timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
+ });
+
+ it('should maintain stack trace', () => {
+ const error = new AppError('Test error');
+
+ expect(error.stack).toBeDefined();
+ expect(typeof error.stack).toBe('string');
+ });
+
+ describe('toJSON', () => {
+ it('should serialize to plain object', () => {
+ const context = { test: 'value' };
+ const error = new AppError('Test error', ErrorCode.NETWORK, context);
+
+ const json = error.toJSON();
+
+ expect(json).not.toBeInstanceOf(AppError);
+ expect(json).toEqual({
+ name: 'AppError',
+ message: 'Test error',
+ code: ErrorCode.NETWORK,
+ context: { test: 'value' },
+ timestamp: error.timestamp,
+ stack: error.stack,
+ originalError: undefined
+ });
+ });
+
+ it('should serialize original error', () => {
+ const originalError = new Error('Original');
+ const error = new AppError('Test', ErrorCode.UNKNOWN, undefined, originalError);
+
+ const json = error.toJSON();
+
+ expect(json.originalError).toEqual({
+ name: 'Error',
+ message: 'Original',
+ stack: originalError.stack
+ });
+ });
+
+ it('should handle missing original error', () => {
+ const error = new AppError('Test');
+ const json = error.toJSON();
+
+ expect(json.originalError).toBeUndefined();
+ });
+ });
+
+ describe('getUserMessage', () => {
+ it('should return user-friendly message for PRINTER_NOT_CONNECTED', () => {
+ const error = new AppError('Technical message', ErrorCode.PRINTER_NOT_CONNECTED);
+ expect(error.getUserMessage()).toBe('Please connect to a printer first');
+ });
+
+ it('should return user-friendly message for PRINTER_BUSY', () => {
+ const error = new AppError('Technical message', ErrorCode.PRINTER_BUSY);
+ expect(error.getUserMessage()).toBe('Printer is busy. Please wait for the current operation to complete');
+ });
+
+ it('should return user-friendly message for PRINTER_ERROR', () => {
+ const error = new AppError('Technical message', ErrorCode.PRINTER_ERROR);
+ expect(error.getUserMessage()).toBe('Printer reported an error. Please check the printer display');
+ });
+
+ it('should return user-friendly message for FILE_NOT_FOUND', () => {
+ const error = new AppError('Technical message', ErrorCode.FILE_NOT_FOUND);
+ expect(error.getUserMessage()).toBe('File not found. Please check the file path');
+ });
+
+ it('should return user-friendly message for NETWORK', () => {
+ const error = new AppError('Technical message', ErrorCode.NETWORK);
+ expect(error.getUserMessage()).toBe('Network error. Please check your connection');
+ });
+
+ it('should return user-friendly message for TIMEOUT', () => {
+ const error = new AppError('Technical message', ErrorCode.TIMEOUT);
+ expect(error.getUserMessage()).toBe('Operation timed out. Please try again');
+ });
+
+ it('should return original message for unknown error codes', () => {
+ const error = new AppError('Custom error message', ErrorCode.UNKNOWN);
+ expect(error.getUserMessage()).toBe('Custom error message');
+ });
+ });
+});
+
+describe('Error Factory Functions', () => {
+ describe('fromZodError', () => {
+ it('should create AppError from ZodError', () => {
+ const zodError = new ZodError([
+ {
+ code: 'invalid_type',
+ expected: 'string',
+ received: 'number',
+ path: ['name'],
+ message: 'Expected string, received number'
+ } as any
+ ]);
+
+ const appError = fromZodError(zodError);
+
+ expect(appError).toBeInstanceOf(AppError);
+ expect(appError.code).toBe(ErrorCode.VALIDATION);
+ expect(appError.message).toBe('Validation failed');
+ expect(appError.context).toBeDefined();
+ expect(appError.context?.issues).toEqual([
+ {
+ path: 'name',
+ message: 'Expected string, received number',
+ code: 'invalid_type'
+ }
+ ]);
+ });
+
+ it('should allow custom error code', () => {
+ const zodError = new ZodError([]);
+ const appError = fromZodError(zodError, ErrorCode.CONFIG_INVALID);
+
+ expect(appError.code).toBe(ErrorCode.CONFIG_INVALID);
+ });
+ });
+
+ describe('networkError', () => {
+ it('should create network error', () => {
+ const error = networkError('Connection failed');
+
+ expect(error).toBeInstanceOf(AppError);
+ expect(error.code).toBe(ErrorCode.NETWORK);
+ expect(error.message).toBe('Connection failed');
+ });
+
+ it('should include context', () => {
+ const error = networkError('Connection failed', { host: 'example.com', port: 80 });
+
+ expect(error.context).toEqual({ host: 'example.com', port: 80 });
+ });
+ });
+
+ describe('timeoutError', () => {
+ it('should create timeout error', () => {
+ const error = timeoutError('fetchData', 5000);
+
+ expect(error).toBeInstanceOf(AppError);
+ expect(error.code).toBe(ErrorCode.TIMEOUT);
+ expect(error.message).toBe('Operation timed out after 5000ms');
+ expect(error.context).toEqual({ operation: 'fetchData', timeoutMs: 5000 });
+ });
+ });
+
+ describe('printerError', () => {
+ it('should create printer error', () => {
+ const error = printerError('Printer offline');
+
+ expect(error).toBeInstanceOf(AppError);
+ expect(error.code).toBe(ErrorCode.PRINTER_ERROR);
+ expect(error.message).toBe('Printer offline');
+ });
+
+ it('should allow custom error code', () => {
+ const error = printerError('Not connected', ErrorCode.PRINTER_NOT_CONNECTED);
+
+ expect(error.code).toBe(ErrorCode.PRINTER_NOT_CONNECTED);
+ });
+
+ it('should include context', () => {
+ const error = printerError('Error', ErrorCode.PRINTER_ERROR, { printerId: '123' });
+
+ expect(error.context).toEqual({ printerId: '123' });
+ });
+ });
+
+ describe('backendError', () => {
+ it('should create backend error', () => {
+ const error = backendError('Operation failed', 'getStatus');
+
+ expect(error).toBeInstanceOf(AppError);
+ expect(error.code).toBe(ErrorCode.BACKEND_OPERATION_FAILED);
+ expect(error.message).toBe('Operation failed');
+ expect(error.context).toEqual({ operation: 'getStatus' });
+ });
+
+ it('should merge additional context', () => {
+ const error = backendError('Failed', 'getStatus', { attempt: 3 });
+
+ expect(error.context).toEqual({ operation: 'getStatus', attempt: 3 });
+ });
+ });
+
+ describe('fileError', () => {
+ it('should create file error', () => {
+ const error = fileError('Invalid format', 'test.gcode');
+
+ expect(error).toBeInstanceOf(AppError);
+ expect(error.code).toBe(ErrorCode.FILE_INVALID_FORMAT);
+ expect(error.message).toBe('Invalid format');
+ expect(error.context).toEqual({ fileName: 'test.gcode' });
+ });
+
+ it('should allow custom error code', () => {
+ const error = fileError('Not found', 'test.gcode', ErrorCode.FILE_NOT_FOUND);
+
+ expect(error.code).toBe(ErrorCode.FILE_NOT_FOUND);
+ });
+ });
+});
+
+describe('Error Handling Utilities', () => {
+ describe('isAppError', () => {
+ it('should return true for AppError instances', () => {
+ const error = new AppError('Test', ErrorCode.NETWORK);
+ expect(isAppError(error)).toBe(true);
+ });
+
+ it('should return false for regular errors', () => {
+ const error = new Error('Test');
+ expect(isAppError(error)).toBe(false);
+ });
+
+ it('should return false for non-error values', () => {
+ expect(isAppError('string')).toBe(false);
+ expect(isAppError(null)).toBe(false);
+ expect(isAppError(undefined)).toBe(false);
+ expect(isAppError({})).toBe(false);
+ });
+ });
+
+ describe('toAppError', () => {
+ it('should return AppError as-is', () => {
+ const original = new AppError('Test', ErrorCode.NETWORK);
+ const converted = toAppError(original);
+
+ expect(converted).toBe(original);
+ });
+
+ it('should convert ZodError to AppError', () => {
+ const zodError = new ZodError([
+ {
+ code: 'invalid_type',
+ expected: 'string',
+ received: 'number',
+ path: ['test'],
+ message: 'Test error'
+ } as any
+ ]);
+
+ const converted = toAppError(zodError);
+
+ expect(converted).toBeInstanceOf(AppError);
+ expect(converted.code).toBe(ErrorCode.VALIDATION);
+ expect(converted.context?.issues).toBeDefined();
+ });
+
+ it('should convert Error to AppError', () => {
+ const original = new Error('Test error');
+ const converted = toAppError(original);
+
+ expect(converted).toBeInstanceOf(AppError);
+ expect(converted.message).toBe('Test error');
+ expect(converted.originalError).toBe(original);
+ });
+
+ it('should convert string to AppError', () => {
+ const converted = toAppError('String error');
+
+ expect(converted).toBeInstanceOf(AppError);
+ expect(converted.message).toBe('String error');
+ });
+
+ it('should convert unknown value to AppError', () => {
+ const converted = toAppError({ custom: 'object' });
+
+ expect(converted).toBeInstanceOf(AppError);
+ expect(converted.message).toBe('An unknown error occurred');
+ expect(converted.context).toEqual({ error: { custom: 'object' } });
+ });
+
+ it('should use default error code', () => {
+ const original = new Error('Test');
+ const converted = toAppError(original, ErrorCode.TIMEOUT);
+
+ expect(converted.code).toBe(ErrorCode.TIMEOUT);
+ });
+ });
+
+ describe('withErrorHandling', () => {
+ it('should return result when function succeeds', async () => {
+ const result = await withErrorHandling(async () => 'success');
+
+ expect(result).toBe('success');
+ });
+
+ it('should return null when function throws', async () => {
+ const result = await withErrorHandling(async () => {
+ throw new Error('Test error');
+ });
+
+ expect(result).toBeNull();
+ });
+
+ it('should call error handler when function throws', async () => {
+ const errorHandler = jest.fn();
+ const error = new Error('Test error');
+
+ await withErrorHandling(
+ async () => {
+ throw error;
+ },
+ errorHandler
+ );
+
+ expect(errorHandler).toHaveBeenCalledWith(expect.any(AppError));
+ expect(errorHandler.mock.calls[0][0] as AppError).toBeInstanceOf(AppError);
+ expect((errorHandler.mock.calls[0][0] as AppError).originalError).toBe(error);
+ });
+
+ it('should log error when no handler provided', async () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ await withErrorHandling(async () => {
+ throw new Error('Test');
+ });
+
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ consoleErrorSpy.mockRestore();
+ });
+ });
+
+ describe('createErrorResult', () => {
+ it('should create error result from AppError', () => {
+ const error = new AppError('Technical message', ErrorCode.PRINTER_NOT_CONNECTED);
+ const result = createErrorResult(error);
+
+ expect(result).toEqual({
+ success: false,
+ error: 'Please connect to a printer first'
+ });
+ });
+
+ it('should create error result from regular error', () => {
+ const error = new Error('Test error');
+ const result = createErrorResult(error);
+
+ expect(result).toEqual({
+ success: false,
+ error: 'Test error'
+ });
+ });
+
+ it('should create error result from string', () => {
+ const result = createErrorResult('String error');
+
+ expect(result).toEqual({
+ success: false,
+ error: 'String error'
+ });
+ });
+
+ it('should create error result from ZodError', () => {
+ const zodError = new ZodError([
+ {
+ code: 'invalid_type',
+ expected: 'string',
+ received: 'number',
+ path: ['test'],
+ message: 'Validation failed'
+ } as any
+ ]);
+
+ const result = createErrorResult(zodError);
+
+ expect(result.success).toBe(false);
+ expect(result.error).toBe('Validation failed');
+ });
+ });
+
+ describe('logError', () => {
+ it('should log error with context', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ const error = new AppError('Test', ErrorCode.NETWORK, { port: 3000 });
+ const additionalContext = { operation: 'connect' };
+
+ logError(error, additionalContext);
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
+ 'Error occurred:',
+ expect.objectContaining({
+ name: 'AppError',
+ message: 'Test',
+ code: ErrorCode.NETWORK,
+ additionalContext: { operation: 'connect' }
+ })
+ );
+
+ consoleErrorSpy.mockRestore();
+ });
+
+ it('should log regular errors', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ const error = new Error('Regular error');
+
+ logError(error);
+
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ expect(consoleErrorSpy.mock.calls[0][1].message).toBe('Regular error');
+
+ consoleErrorSpy.mockRestore();
+ });
+
+ it('should work without additional context', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ const error = new AppError('Test', ErrorCode.NETWORK);
+
+ logError(error);
+
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ consoleErrorSpy.mockRestore();
+ });
+ });
+});
diff --git a/src/webui/server/WebUIManager.integration.test.ts b/src/webui/server/WebUIManager.integration.test.ts
new file mode 100644
index 0000000..6838612
--- /dev/null
+++ b/src/webui/server/WebUIManager.integration.test.ts
@@ -0,0 +1,374 @@
+/**
+ * @fileoverview Integration tests for WebUIManager
+ * Tests middleware order, static file serving, SPA routing, and API endpoints
+ */
+
+import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, jest } from '@jest/globals';
+import express, { Express } from 'express';
+import * as fs from 'fs';
+import * as path from 'path';
+import request from 'supertest';
+import { EnvironmentService } from '../../services/EnvironmentService';
+import { ConfigManager } from '../../managers/ConfigManager';
+import { StandardAPIResponse } from '../types/web-api.types';
+
+// Mock the singleton dependencies
+jest.mock('../../services/EnvironmentService');
+jest.mock('../../managers/ConfigManager');
+jest.mock('./AuthManager');
+jest.mock('./WebSocketManager');
+
+describe('WebUIManager Integration Tests', () => {
+ let app: Express;
+ let mockEnvironmentService: jest.Mocked;
+ let mockConfigManager: jest.Mocked;
+
+ // Mock static file path
+ const mockStaticPath = path.join(__dirname, '../static/mock-webui');
+
+ beforeAll(() => {
+ // Create mock static directory structure
+ if (!fs.existsSync(mockStaticPath)) {
+ fs.mkdirSync(mockStaticPath, { recursive: true });
+ }
+
+ // Create a mock index.html
+ const mockIndexHtml = 'Mock WebUI';
+ fs.writeFileSync(path.join(mockStaticPath, 'index.html'), mockIndexHtml);
+
+ // Create a mock CSS file
+ const mockCss = 'body { margin: 0; }';
+ fs.writeFileSync(path.join(mockStaticPath, 'styles.css'), mockCss);
+
+ // Create a mock JS file
+ const mockJs = 'console.log("test");';
+ fs.writeFileSync(path.join(mockStaticPath, 'app.js'), mockJs);
+ });
+
+ afterAll(() => {
+ // Cleanup mock directory
+ if (fs.existsSync(mockStaticPath)) {
+ fs.rmSync(mockStaticPath, { recursive: true, force: true });
+ }
+ });
+
+ beforeEach(() => {
+ // Setup mocks
+ mockEnvironmentService = {
+ isPackaged: jest.fn().mockReturnValue(false),
+ isProduction: jest.fn().mockReturnValue(false),
+ isDevelopment: jest.fn().mockReturnValue(true),
+ getWebUIStaticPath: jest.fn().mockReturnValue(mockStaticPath),
+ getEnvironmentInfo: jest.fn().mockReturnValue({
+ isPackaged: false,
+ isProduction: false,
+ isDevelopment: true,
+ dirname: __dirname,
+ cwd: process.cwd(),
+ staticPath: mockStaticPath,
+ dataPath: path.join(process.cwd(), 'data')
+ }),
+ getDataPath: jest.fn().mockReturnValue(path.join(process.cwd(), 'data')),
+ getLogsPath: jest.fn().mockReturnValue(path.join(process.cwd(), 'data', 'logs')),
+ getAppRootPath: jest.fn().mockReturnValue(process.cwd()),
+ isElectron: jest.fn().mockReturnValue(false)
+ } as any;
+
+ mockConfigManager = {
+ getConfig: jest.fn().mockReturnValue({
+ WebUIEnabled: true,
+ WebUIPort: 3001,
+ WebUIPassword: 'testpass',
+ WebUIPasswordRequired: true,
+ SpoolmanEnabled: false,
+ SpoolmanServerUrl: '',
+ CameraProxyPort: 8181
+ }),
+ get: jest.fn().mockReturnValue(true),
+ on: jest.fn()
+ } as any;
+
+ // Mock the module imports
+ jest.doMock('../../services/EnvironmentService', () => ({
+ getEnvironmentService: () => mockEnvironmentService
+ }));
+
+ jest.doMock('../../managers/ConfigManager', () => ({
+ getConfigManager: () => mockConfigManager
+ }));
+
+ // Create a minimal Express app with the same middleware structure
+ app = express();
+
+ // JSON body parsing
+ app.use(express.json());
+
+ // Static file serving
+ app.use(express.static(mockStaticPath, {
+ fallthrough: true,
+ maxAge: '0'
+ }));
+
+ // Mock API routes
+ app.get('/api/health', (_req, res) => {
+ res.json({ success: true, status: 'ok' });
+ });
+
+ // 404 handler for API routes - must come after specific API routes
+ app.use('/api', (req, res) => {
+ // Only handle if no specific route matched
+ // Note: req.path doesn't include the /api prefix when mounted at /api
+ const fullPath = `/api${req.path}`;
+ const response: StandardAPIResponse = {
+ success: false,
+ error: `API endpoint not found: ${req.method} ${fullPath}`
+ };
+ res.status(404).json(response);
+ });
+
+ // SPA fallback - using middleware approach for Express 5.x compatibility
+ // This must come after all other routes
+ app.use((req, res, next) => {
+ // Only handle GET requests for SPA routes
+ if (req.method !== 'GET') {
+ return next();
+ }
+
+ // Skip API routes
+ if (req.path.startsWith('/api')) {
+ return next();
+ }
+
+ // Serve index.html for SPA routes (no extension or root)
+ const indexPath = path.join(mockStaticPath, 'index.html');
+
+ // If path has no extension (SPA route), serve index.html
+ if (!path.extname(req.path) || req.path === '/') {
+ res.sendFile(indexPath);
+ return;
+ }
+
+ // File with extension that wasn't found by static middleware
+ const response: StandardAPIResponse = {
+ success: false,
+ error: `File not found: ${req.path}`
+ };
+ res.status(404).json(response);
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('Static File Serving', () => {
+ it('should serve index.html at root path', async () => {
+ const response = await request(app).get('/');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ expect(response.headers['content-type']).toContain('text/html');
+ });
+
+ it('should serve CSS files', async () => {
+ const response = await request(app).get('/styles.css');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('body { margin: 0; }');
+ expect(response.headers['content-type']).toContain('text/css');
+ });
+
+ it('should serve JavaScript files', async () => {
+ const response = await request(app).get('/app.js');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('console.log("test")');
+ expect(response.headers['content-type']).toContain('javascript');
+ });
+
+ it('should return 404 for non-existent static files', async () => {
+ const response = await request(app).get('/nonexistent.css');
+
+ expect(response.status).toBe(404);
+ expect(response.body).toEqual({
+ success: false,
+ error: 'File not found: /nonexistent.css'
+ });
+ });
+ });
+
+ describe('API Routes', () => {
+ it('should return 200 for valid API endpoint', async () => {
+ const response = await request(app).get('/api/health');
+
+ expect(response.status).toBe(200);
+ expect(response.body).toEqual({
+ success: true,
+ status: 'ok'
+ });
+ });
+
+ it('should return 404 for non-existent API endpoint', async () => {
+ const response = await request(app).get('/api/nonexistent');
+
+ expect(response.status).toBe(404);
+ expect(response.body).toEqual({
+ success: false,
+ error: 'API endpoint not found: GET /api/nonexistent'
+ });
+ });
+
+ it('should return 404 for non-existent API endpoint with different method', async () => {
+ const response = await request(app).post('/api/test');
+
+ expect(response.status).toBe(404);
+ expect(response.body).toEqual({
+ success: false,
+ error: 'API endpoint not found: POST /api/test'
+ });
+ });
+ });
+
+ describe('SPA Routing', () => {
+ it('should serve index.html for SPA routes without extension', async () => {
+ const response = await request(app).get('/dashboard');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ });
+
+ it('should serve index.html for nested SPA routes', async () => {
+ const response = await request(app).get('/printer/settings');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ });
+
+ it('should serve index.html for routes with query parameters', async () => {
+ const response = await request(app).get('/settings?tab=general');
+
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ });
+
+ it('should return 404 JSON for missing files with extensions', async () => {
+ const response = await request(app).get('/missing.js');
+
+ expect(response.status).toBe(404);
+ expect(response.body).toEqual({
+ success: false,
+ error: 'File not found: /missing.js'
+ });
+ });
+
+ it('should not serve index.html for requests with file extensions', async () => {
+ // This test verifies that file requests don't fall through to SPA
+ const response = await request(app).get('/test.json');
+
+ expect(response.status).toBe(404);
+ expect(response.headers['content-type']).not.toContain('text/html');
+ });
+ });
+
+ describe('Middleware Order', () => {
+ it('should process middleware in correct order: static -> API -> SPA fallback', async () => {
+ // Test static file middleware (first in chain)
+ const staticResponse = await request(app).get('/styles.css');
+ expect(staticResponse.status).toBe(200);
+ expect(staticResponse.text).toContain('margin');
+
+ // Test API middleware (second in chain)
+ const apiResponse = await request(app).get('/api/health');
+ expect(apiResponse.status).toBe(200);
+ expect(apiResponse.body.success).toBe(true);
+
+ // Test SPA fallback (last in chain)
+ const spaResponse = await request(app).get('/dashboard');
+ expect(spaResponse.status).toBe(200);
+ expect(spaResponse.text).toContain('Mock WebUI');
+ });
+
+ it('should handle 404 for API routes before SPA fallback', async () => {
+ const response = await request(app).get('/api/notfound');
+
+ // Should return JSON 404 from API middleware, not HTML from SPA
+ expect(response.status).toBe(404);
+ expect(response.body.success).toBe(false);
+ expect(response.body.error).toContain('API endpoint not found');
+ expect(response.headers['content-type']).toContain('application/json');
+ });
+ });
+
+ describe('Cache Headers', () => {
+ it('should respect cache headers configuration', async () => {
+ // In development (isProduction: false), cache should be disabled
+ const response = await request(app).get('/styles.css');
+
+ // Cache header should be 'no-cache' or similar when maxAge is 0
+ const cacheControl = response.headers['cache-control'];
+ expect(cacheControl).toBeDefined();
+ });
+ });
+
+ describe('Content-Type Headers', () => {
+ it('should return correct content-type for HTML', async () => {
+ const response = await request(app).get('/');
+ expect(response.headers['content-type']).toContain('text/html');
+ });
+
+ it('should return correct content-type for CSS', async () => {
+ const response = await request(app).get('/styles.css');
+ expect(response.headers['content-type']).toContain('text/css');
+ });
+
+ it('should return correct content-type for JavaScript', async () => {
+ const response = await request(app).get('/app.js');
+ expect(response.headers['content-type']).toContain('javascript');
+ });
+
+ it('should return correct content-type for API JSON responses', async () => {
+ const response = await request(app).get('/api/health');
+ expect(response.headers['content-type']).toContain('application/json');
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle root path with trailing slash', async () => {
+ const response = await request(app).get('/');
+ expect(response.status).toBe(200);
+ });
+
+ it('should handle deeply nested SPA routes', async () => {
+ const response = await request(app).get('/a/b/c/d/e/f');
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ });
+
+ it('should handle special characters in routes', async () => {
+ const response = await request(app).get('/settings?tab=general&theme=dark');
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ });
+ });
+
+ describe('Fallback Behavior', () => {
+ it('should fallback to index.html for unmatched routes without extensions', async () => {
+ const routes = ['/dashboard', '/printer/123', '/settings', '/about'];
+
+ for (const route of routes) {
+ const response = await request(app).get(route);
+ expect(response.status).toBe(200);
+ expect(response.text).toContain('Mock WebUI');
+ }
+ });
+
+ it('should not fallback to index.html for API routes', async () => {
+ const response = await request(app).get('/api/test');
+ expect(response.status).toBe(404);
+ expect(response.body.success).toBe(false);
+ });
+ });
+});
+
+// Note: EnvironmentService is fully tested in EnvironmentService.test.ts
+// These integration tests focus on WebUIManager middleware and routing
diff --git a/src/webui/server/WebUIManager.ts b/src/webui/server/WebUIManager.ts
index 0e68152..841fab9 100644
--- a/src/webui/server/WebUIManager.ts
+++ b/src/webui/server/WebUIManager.ts
@@ -23,7 +23,9 @@ import * as http from 'http';
import express from 'express';
import * as os from 'os';
import * as path from 'path';
+import * as fs from 'fs';
import { getConfigManager } from '../../managers/ConfigManager';
+import { getEnvironmentService } from '../../services/EnvironmentService';
import { AppError, ErrorCode } from '../../utils/error.utils';
import { getAuthManager } from './AuthManager';
@@ -84,9 +86,10 @@ export class WebUIManager extends EventEmitter {
// Server components (will be initialized later)
private expressApp: express.Application | null = null;
private httpServer: http.Server | null = null;
-
+
// Server state
private isRunning: boolean = false;
+ private isStopping: boolean = false;
private serverIP: string = 'localhost';
private port: number = 3000;
@@ -96,6 +99,9 @@ export class WebUIManager extends EventEmitter {
private readonly registeredContexts: Set = new Set();
private readonly contextSerialNumbers: Map = new Map();
+ // Static file path for SPA fallback
+ private webUIStaticPath: string = '';
+
// Initialization control - prevent auto-start during app initialization
private allowAutoStart = false;
@@ -146,16 +152,45 @@ export class WebUIManager extends EventEmitter {
// JSON body parsing
this.expressApp.use(express.json());
- // Static file serving - serve from dist/webui/static directory
- const webUIStaticPath = path.join(process.cwd(), 'dist', 'webui', 'static');
- console.log(`WebUI serving static files from: ${webUIStaticPath}`);
+ // Static file serving - use EnvironmentService for correct path resolution
+ const environmentService = getEnvironmentService();
+ const webUIStaticPath = environmentService.getWebUIStaticPath();
+
+ // Log environment info for debugging
+ const envInfo = environmentService.getEnvironmentInfo();
+ console.log(`[WebUI] Environment: ${envInfo.isPackaged ? 'packaged binary' : 'development'}`);
+ console.log(`[WebUI] Serving static files from: ${webUIStaticPath}`);
+
+ // Verify the static path exists
+ if (!fs.existsSync(webUIStaticPath)) {
+ console.error(`[WebUI] Static file path does not exist: ${webUIStaticPath}`);
+ console.error('[WebUI] Environment details:', JSON.stringify(envInfo, null, 2));
+
+ if (!envInfo.isPackaged) {
+ console.error('[WebUI] Hint: Run "npm run build" to compile the WebUI assets');
+ }
+
+ throw new AppError(
+ `WebUI static files not found at: ${webUIStaticPath}`,
+ ErrorCode.CONFIG_INVALID,
+ { webUIStaticPath, environment: envInfo },
+ undefined
+ );
+ }
try {
- this.expressApp.use(express.static(webUIStaticPath));
- console.log('WebUI static file middleware configured successfully');
+ this.expressApp.use(express.static(webUIStaticPath, {
+ // Enable fallthrough for SPA routing (handled separately)
+ fallthrough: true,
+ // Set reasonable cache headers
+ maxAge: envInfo.isProduction ? '1d' : 0
+ }));
+ console.log('[WebUI] Static file middleware configured successfully');
+
+ // Store static path for SPA fallback
+ this.webUIStaticPath = webUIStaticPath;
} catch (error) {
- console.error('Failed to configure WebUI static file serving:', error);
- console.error(`Attempted path: ${webUIStaticPath}`);
+ console.error('[WebUI] Failed to configure static file serving:', error);
throw new AppError(
`Failed to configure WebUI static file serving from path: ${webUIStaticPath}`,
ErrorCode.CONFIG_INVALID,
@@ -186,6 +221,46 @@ export class WebUIManager extends EventEmitter {
const apiRoutes = createAPIRoutes(routeDependencies);
this.expressApp.use('/api', apiRoutes);
+ // 404 handler for API routes - must come before SPA fallback
+ this.expressApp.use('/api/*splat', (req, res) => {
+ const response: StandardAPIResponse = {
+ success: false,
+ error: `API endpoint not found: ${req.method} ${req.originalUrl}`
+ };
+ res.status(404).json(response);
+ });
+
+ // SPA fallback - serve index.html for non-API routes that don't match static files
+ // NOTE: This app does NOT use client-side routing. All UI state is managed via DOM manipulation.
+ // The fallback ensures page refreshes and direct URL access work correctly.
+ // Using path.extname() to detect file requests is safe since there are no client-side routes.
+ // If client-side routing is added in the future, this should use Accept header detection instead.
+ this.expressApp.get('/*splat', (req, res, next) => {
+ // Skip if this looks like a file request with extension (handled by static middleware)
+ if (path.extname(req.path) && req.path !== '/') {
+ // File request that wasn't found by static middleware - return 404
+ const response: StandardAPIResponse = {
+ success: false,
+ error: `File not found: ${req.path}`
+ };
+ res.status(404).json(response);
+ return;
+ }
+
+ // Serve index.html for SPA routes
+ const indexPath = path.join(this.webUIStaticPath, 'index.html');
+ if (fs.existsSync(indexPath)) {
+ res.sendFile(indexPath);
+ } else {
+ // index.html is missing - this indicates a build/deployment error
+ next(new AppError(
+ 'WebUI application files not found. Please ensure the application has been built properly.',
+ ErrorCode.CONFIG_INVALID,
+ { indexPath, staticPath: this.webUIStaticPath }
+ ));
+ }
+ });
+
// Error handling (must be last)
this.expressApp.use(createErrorMiddleware());
}
@@ -357,34 +432,65 @@ export class WebUIManager extends EventEmitter {
/**
* Stop the web UI server
*/
- public async stop(): Promise {
+ public async stop(timeoutMs = 3000): Promise {
+ // Guard against concurrent stop calls
+ if (this.isStopping) {
+ console.warn('[WebUI] Stop already in progress');
+ return false;
+ }
+ this.isStopping = true;
+
+ const startTime = Date.now();
+
try {
-
if (this.httpServer) {
- await new Promise((resolve) => {
- this.httpServer!.close(() => {
- console.log('WebUI server stopped');
- resolve();
- });
- });
-
- this.httpServer = null;
+ try {
+ // Race server close against timeout
+ await Promise.race([
+ new Promise((resolve) => {
+ this.httpServer!.close(() => {
+ console.log('WebUI server stopped');
+ resolve();
+ });
+ }),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Server close timeout')), timeoutMs)
+ )
+ ]);
+ } catch (error) {
+ if (error instanceof Error && error.message === 'Server close timeout') {
+ console.warn(`[WebUI] Server close timed out after ${timeoutMs}ms - forcing connections closed`);
+ // Force close all connections (Node.js >= 18.18.0, project requires >= 20.0.0)
+ this.httpServer.closeAllConnections();
+ } else {
+ throw error;
+ }
}
-
- // Shutdown WebSocket server
- this.webSocketManager.shutdown();
-
- this.expressApp = null;
- this.isRunning = false;
- this.connectedClients = 0;
-
+
+ this.httpServer = null;
+ }
+
+ // Shutdown WebSocket server
+ this.webSocketManager.shutdown();
+
+ this.expressApp = null;
+ this.isRunning = false;
+ this.connectedClients = 0;
+
this.emit('server-stopped');
-
+
+ const duration = Date.now() - startTime;
+ if (duration > 1000) {
+ console.log(`[WebUI] Stop completed in ${duration}ms`);
+ }
+
return true;
-
+
} catch (error) {
console.error('Error stopping WebUI server:', error);
return false;
+ } finally {
+ this.isStopping = false;
}
}
diff --git a/tsconfig.json b/tsconfig.json
index ce84aa5..9a5c4af 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -28,7 +28,9 @@
"src/services/**/*.ts",
"src/types/**/*.ts",
"src/utils/**/*.ts",
- "src/webui/**/*.ts"
+ "src/webui/**/*.ts",
+ "src/__tests__/**/*.ts",
+ "**/*.test.ts"
],
"exclude": [
"node_modules",