diff --git a/.changeset/add-nuxt-module.md b/.changeset/add-nuxt-module.md new file mode 100644 index 0000000..28d2608 --- /dev/null +++ b/.changeset/add-nuxt-module.md @@ -0,0 +1,16 @@ +--- +"@csrf-armor/nuxt": minor +--- + +Add `@csrf-armor/nuxt` module for Nuxt 3/4 applications + +Introduces a new Nuxt module that provides server-side CSRF protection via a Nitro middleware and client-side utilities for token management. + +**Features:** +- `NuxtAdapter` bridges H3 events with the framework-agnostic `@csrf-armor/core` engine +- Server middleware automatically enforces CSRF protection on all mutating requests +- `useCsrfToken` composable for SSR-safe token access via `useState` +- `useCsrfFetch` composable wrapping `$fetch` with automatic CSRF token injection +- Client plugin initialises the token on page load +- Full support for all core strategies: `double-submit`, `signed-double-submit`, `signed-token`, `origin-check`, `hybrid` +- Zero runtime dependencies β€” uses H3Event native Web API (`event.method`, `event.headers`, `event.path`) and Node.js built-ins instead of h3 helper functions diff --git a/package.json b/package.json index b455f53..e6c9a9e 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,16 @@ "brace-expansion": "^2.0.2", "tmp": ">=0.2.4", "vite": "^6.4.1", - "qs": ">=6.14.1" + "qs": ">=6.14.2", + "simple-git": ">=3.32.3", + "rollup": ">=4.59.0", + "serialize-javascript": ">=7.0.3", + "svgo": ">=4.0.1", + "tar": ">=7.5.11", + "devalue": ">=5.6.3", + "minimatch@>=5.0.0 <6.0.0": "5.1.9", + "minimatch@>=9.0.0 <10.0.0": "9.0.9", + "minimatch@>=10.0.0 <11.0.0": "10.2.4" } }, "packageManager": "pnpm@10.2.1+sha512.398035c7bd696d0ba0b10a688ed558285329d27ea994804a52bad9167d8e3a72bcb993f9699585d3ca25779ac64949ef422757a6c31102c12ab932e5cbe5cc92" diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md new file mode 100644 index 0000000..d1de52c --- /dev/null +++ b/packages/nuxt/README.md @@ -0,0 +1,369 @@ +# @csrf-armor/nuxt + +CSRF Armor + +[![CI](https://github.com/muneebs/csrf-armor/workflows/CI/badge.svg)](https://github.com/muneebs/csrf-armor/actions/workflows/ci.yml) +[![npm version](https://badge.fury.io/js/@csrf-armor%2Fnuxt.svg)](https://badge.fury.io/js/@csrf-armor%2Fnuxt) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) +[![Nuxt](https://img.shields.io/badge/Nuxt-3%2B%20%7C%204%2B-00DC82.svg)](https://nuxt.com/) + +**Complete CSRF protection for Nuxt 3/4 applications with automatic server middleware, Vue composables, and SSR-safe token management.** + +## Contents + +- [Features](#-features) +- [Quick Start](#-quick-start) +- [Configuration](#-configuration) +- [Composables](#-composables) +- [Security Strategies](#-security-strategies) +- [Security Best Practices](#-security-best-practices) + +## ✨ Features + +- πŸ›‘οΈ **Multiple Security Strategies** - Choose from 5 different CSRF protection methods +- πŸ”„ **Auto-registered Middleware** - Server middleware applied automatically to all routes +- πŸͺ **Vue Composables** - `useCsrfToken` and `useCsrfFetch` auto-imported in your components +- 🎯 **TypeScript First** - Fully typed with comprehensive TypeScript support +- πŸ“± **SSR-Safe** - Uses Nuxt's `useState` for request-isolated server-side state +- ⚑ **Zero Runtime Dependencies** - Uses H3Event native API with no extra runtime packages + +--- + +## πŸš€ Quick Start + +### 1. Installation + +```bash +npm install @csrf-armor/nuxt +# or +yarn add @csrf-armor/nuxt +# or +pnpm add @csrf-armor/nuxt +``` + +### 2. Register the Module + +Add `@csrf-armor/nuxt` to your `nuxt.config.ts`: + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@csrf-armor/nuxt'], + + csrfArmor: { + strategy: 'signed-double-submit', + secret: process.env.CSRF_SECRET, + cookie: { + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + }, + }, +}); +``` + +### 3. Environment Setup + +Add to your `.env`: + +```bash +# Generate with: openssl rand -base64 32 +CSRF_SECRET=your-super-secret-csrf-key-min-32-chars-long +``` + +> **⚠️ Security Warning**: Never use a default or weak secret in production! + +That's it. The module automatically registers a Nitro server middleware that enforces CSRF protection on all mutating requests (POST, PUT, PATCH, DELETE). + +### 4. Use in Components + +`useCsrfToken` and `useCsrfFetch` are auto-imported β€” no explicit import needed: + +```vue + + + +``` + +### 5. Using with useFetch + +For data fetching with Nuxt's `useFetch`, use `useCsrfFetch` instead: + +```vue + +``` + +--- + +## βš™οΈ Configuration + +Configuration is set via `csrfArmor` in `nuxt.config.ts`. All options from `@csrf-armor/core` are supported. + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@csrf-armor/nuxt'], + + csrfArmor: { + strategy: 'signed-double-submit', // CSRF strategy + secret: process.env.CSRF_SECRET, // Required for signed strategies + + token: { + expiry: 3600, // Token lifetime in seconds (default: 3600) + headerName: 'x-csrf-token', // Header to read/send token (default: 'x-csrf-token') + fieldName: 'csrf_token', // Form field name (default: 'csrf_token') + }, + + cookie: { + name: 'csrf-token', // Cookie name (default: 'csrf-token') + secure: true, // HTTPS only (default: true in production) + httpOnly: false, // Allow client access (default: false) + sameSite: 'lax', // SameSite policy (default: 'lax') + path: '/', // Cookie path (default: '/') + maxAge: 86400, // Max age in seconds (optional) + }, + + excludePaths: ['/api/webhooks'], // Paths excluded from CSRF protection + allowedOrigins: ['https://yourdomain.com'], // For origin-check strategy + }, +}); +``` + +### Environment-Specific Configuration + +```typescript +// nuxt.config.ts +const isDev = process.env.NODE_ENV !== 'production'; + +export default defineNuxtConfig({ + modules: ['@csrf-armor/nuxt'], + + csrfArmor: { + strategy: isDev ? 'double-submit' : 'signed-double-submit', + secret: process.env.CSRF_SECRET, + cookie: { + secure: !isDev, + sameSite: 'lax', + }, + }, +}); +``` + +### Accessing the Token Server-Side + +The middleware stores the issued token on `event.context.csrfToken` for use in server routes: + +```typescript +// server/api/example.post.ts +export default defineEventHandler((event) => { + const csrfToken = event.context.csrfToken; + // token available if needed + return { success: true }; +}); +``` + +### Excluding Paths + +```typescript +csrfArmor: { + excludePaths: [ + '/api/webhooks/stripe', // External webhooks + '/api/public', // Public API endpoints + '/health', // Health checks + ], +}, +``` + +--- + +## πŸͺ Composables + +Both composables are automatically imported by the module β€” no import statement needed. + +### `useCsrfToken()` + +Reactive CSRF token management using Nuxt's `useState` for SSR-safe, request-isolated state. + +```typescript +const { csrfToken, updateToken, csrfFetch } = useCsrfToken(); +``` + +**Returns:** + +- `csrfToken: Ref` β€” Reactive CSRF token, shared across all components in the same request/session +- `updateToken: () => void` β€” Manually re-reads the token from cookies +- `csrfFetch: (input, init?) => Promise` β€” Native `fetch` wrapper that automatically attaches the CSRF header and updates the token from response headers + +Route changes and browser history navigation are observed automatically to keep the token fresh. + +### `useCsrfFetch(url, opts?)` + +A wrapper around Nuxt's `useFetch` that automatically injects the CSRF header on every request. + +```typescript +const { data, pending, error } = await useCsrfFetch('/api/items', { + method: 'POST', + body: { name: 'New Item' }, +}); +``` + +Existing `onRequest` interceptors in `opts` are preserved and chained correctly. + +--- + +## πŸ›‘οΈ Security Strategies + +| Strategy | Security | Performance | Best For | +|----------------------------|-----------|-------------|-----------------------| +| **Signed Double Submit** ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Most web apps | +| **Double Submit** | ⭐ | ⭐⭐⭐⭐⭐ | Local development | +| **Signed Token** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | APIs, SPAs | +| **Origin Check** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Known origins | +| **Hybrid** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Maximum security | + +### Signed Double Submit (Recommended) + +```typescript +csrfArmor: { + strategy: 'signed-double-submit', + secret: process.env.CSRF_SECRET, +} +``` + +Client receives an unsigned token in the response header and a readable cookie. The server stores a signed copy in an httpOnly cookie and verifies submissions against it. Combines cryptographic protection with the double-submit pattern. + +### Double Submit Cookie + +```typescript +csrfArmor: { + strategy: 'double-submit', +} +``` + +Same token stored in cookie and sent in header. Relies on Same-Origin Policy. Suitable for local development only. + +### Signed Token + +```typescript +csrfArmor: { + strategy: 'signed-token', + secret: process.env.CSRF_SECRET, + token: { expiry: 3600 }, +} +``` + +HMAC-signed tokens with expiration timestamps. Stateless validation. Best for APIs and SPAs. + +### Origin Check + +```typescript +csrfArmor: { + strategy: 'origin-check', + allowedOrigins: [ + 'https://yourdomain.com', + 'https://www.yourdomain.com', + ], +} +``` + +Validates `Origin`/`Referer` headers against an allowlist. Lightweight with minimal overhead. + +### Hybrid + +```typescript +csrfArmor: { + strategy: 'hybrid', + secret: process.env.CSRF_SECRET, + allowedOrigins: ['https://yourdomain.com'], +} +``` + +Combines signed token validation with origin checking for maximum security depth. + +--- + +## πŸ”’ Security Best Practices + +### Strong Secret Management + +```bash +# Generate a strong secret +openssl rand -base64 32 + +# Or using Node.js +node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" +``` + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@csrf-armor/nuxt'], + + csrfArmor: { + strategy: 'signed-double-submit', + secret: process.env.CSRF_SECRET, // Never hardcode secrets + }, +}); +``` + +### Cookie Security + +```typescript +csrfArmor: { + cookie: { + secure: process.env.NODE_ENV === 'production', // HTTPS only in production + sameSite: 'strict', // Strictest protection if cross-origin not needed + httpOnly: false, // Must be false so the client plugin can read it + path: '/', + maxAge: 60 * 60 * 24, // 24 hours + }, +}, +``` + +--- + +## 🀝 Contributing + +We welcome contributions! Areas where help is needed: + +- **Additional framework integrations** +- **Performance optimizations** +- **Security enhancements** +- **Documentation improvements** +- **Test coverage expansion** + +--- + +## πŸ“„ License + +MIT Β© [Jordan Labrosse](https://github.com/Jorgagu) + +## πŸ“¦ Related Packages + +- **[@csrf-armor/core](../core)** - Framework-agnostic CSRF protection +- **[@csrf-armor/nextjs](../nextjs)** - Next.js adapter + +--- + +**Questions?** [Open an issue](https://github.com/muneebs/csrf-armor/issues) +or [start a discussion](https://github.com/muneebs/csrf-armor/discussions)! diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index a583d5c..3247a02 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -55,7 +55,6 @@ "devDependencies": { "@nuxt/kit": "^4.3.1", "@nuxt/schema": "^4.3.1", - "defu": "^6.1.4", "h3": "^1.15.3", "nuxt": "^4.3.1", "tsdown": "^0.20.1", @@ -64,7 +63,6 @@ }, "peerDependencies": { "@nuxt/kit": "^3.0.0 || ^4.0.0", - "h3": "^1.0.0", "nuxt": "^3.0.0 || ^4.0.0", "vue": "^3.3.0" }, diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index e1bc74e..c73b128 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,3 +1,4 @@ +import type { CsrfConfig } from '@csrf-armor/core'; import { addImports, addPlugin, @@ -6,8 +7,6 @@ import { defineNuxtModule, } from '@nuxt/kit'; import type { NuxtModule } from '@nuxt/schema'; -import { defu } from 'defu'; -import type { CsrfConfig } from '@csrf-armor/core'; // Re-export core types for consumer convenience export type { @@ -29,6 +28,33 @@ export { export interface ModuleOptions extends CsrfConfig {} +/** + * Deep merges `overrides` into `defaults`, with `overrides` taking priority. + * Only plain objects are merged recursively; arrays and primitives are replaced. + */ +function mergeDefaults(defaults: T, overrides?: Partial | null): T { + if (!overrides) return { ...(defaults as object) } as T; + const result = { ...(defaults as object) } as Record; + const src = overrides as Record; + for (const key of Object.keys(src)) { + const val = src[key]; + if (val === undefined || val === null) continue; + const existing = result[key]; + if ( + typeof val === 'object' && + !Array.isArray(val) && + typeof existing === 'object' && + existing !== null && + !Array.isArray(existing) + ) { + result[key] = mergeDefaults(existing, val); + } else { + result[key] = val; + } + } + return result as T; +} + const module: NuxtModule = defineNuxtModule({ meta: { name: '@csrf-armor/nuxt', @@ -42,24 +68,27 @@ const module: NuxtModule = defineNuxtModule({ const resolver = createResolver(import.meta.url); // Merge module options with any existing runtimeConfig (host app values take priority) - const mergedConfig = defu( + const mergedConfig = mergeDefaults( + options, // biome-ignore lint/complexity/useLiteralKeys: runtimeConfig uses index signatures - nuxt.options.runtimeConfig['csrfArmor'] as Partial | undefined, - options - ) as CsrfConfig; + nuxt.options.runtimeConfig['csrfArmor'] as Partial | undefined + ); // biome-ignore lint/complexity/useLiteralKeys: runtimeConfig uses index signatures nuxt.options.runtimeConfig['csrfArmor'] = mergedConfig; + // biome-ignore lint/complexity/useLiteralKeys: runtimeConfig uses index signatures - nuxt.options.runtimeConfig.public['csrfArmor'] = defu( + nuxt.options.runtimeConfig.public['csrfArmor'] = mergeDefaults( + { + // biome-ignore lint/complexity/useLiteralKeys: CsrfConfig uses index signatures + cookieName: mergedConfig['cookie']?.name ?? 'csrf-token', + // biome-ignore lint/complexity/useLiteralKeys: CsrfConfig uses index signatures + headerName: mergedConfig['token']?.headerName ?? 'x-csrf-token', + }, // biome-ignore lint/complexity/useLiteralKeys: runtimeConfig uses index signatures nuxt.options.runtimeConfig.public['csrfArmor'] as | { cookieName?: string; headerName?: string } - | undefined, - { - cookieName: mergedConfig.cookie?.name ?? 'csrf-token', - headerName: mergedConfig.token?.headerName ?? 'x-csrf-token', - } + | undefined ); // Register server middleware for CSRF protection diff --git a/packages/nuxt/src/runtime/composables/useCsrfToken.ts b/packages/nuxt/src/runtime/composables/useCsrfToken.ts index 55bc543..1f0f5c4 100644 --- a/packages/nuxt/src/runtime/composables/useCsrfToken.ts +++ b/packages/nuxt/src/runtime/composables/useCsrfToken.ts @@ -1,13 +1,13 @@ import { effectScope, watch } from 'vue'; // @ts-expect-error - Nuxt auto-imports resolved at build time import { useRoute, useRuntimeConfig, useState } from '#imports'; +import type { CsrfArmorPublicConfig } from '../types'; import { type CsrfClientConfig, - getCsrfToken, csrfFetch as clientCsrfFetch, + getCsrfToken, refreshCsrfToken, } from '../utils/client'; -import type { CsrfArmorPublicConfig } from '../types'; /** * Detached effect scope for global listeners. @@ -87,12 +87,10 @@ export function useCsrfToken() { | CsrfArmorPublicConfig | undefined; - if (!resolvedConfig) { - resolvedConfig = { - cookieName: publicConfig?.cookieName ?? 'csrf-token', - headerName: publicConfig?.headerName ?? 'x-csrf-token', - }; - } + resolvedConfig ??= { + cookieName: publicConfig?.cookieName ?? 'csrf-token', + headerName: publicConfig?.headerName ?? 'x-csrf-token', + }; const config = resolvedConfig; diff --git a/packages/nuxt/src/runtime/server/adapter.ts b/packages/nuxt/src/runtime/server/adapter.ts index 4baf90d..165618d 100644 --- a/packages/nuxt/src/runtime/server/adapter.ts +++ b/packages/nuxt/src/runtime/server/adapter.ts @@ -1,3 +1,4 @@ +import type { IncomingMessage, ServerResponse } from 'node:http'; import type { CookieOptions, CsrfAdapter, @@ -6,22 +7,96 @@ import type { RequiredCsrfConfig, } from '@csrf-armor/core'; import type { H3Event } from 'h3'; -import { - getHeader, - getHeaders, - getMethod, - getRequestURL, - parseCookies, - readBody, - setCookie, - setResponseHeader, -} from 'h3'; + +/** Parses a raw Cookie header string into a nameβ†’value map. */ +function parseCookieHeader( + cookieHeader: string | null +): Record { + if (!cookieHeader) return {}; + const result: Record = {}; + for (const pair of cookieHeader.split(';')) { + const eqIndex = pair.indexOf('='); + if (eqIndex === -1) continue; + const name = pair.slice(0, eqIndex).trim(); + const value = pair.slice(eqIndex + 1).trim(); + try { + result[name] = decodeURIComponent(value); + } catch { + result[name] = value; + } + } + return result; +} + +/** Serializes a cookie name/value and options into a Set-Cookie header string. */ +function serializeCookie( + name: string, + value: string, + options?: CookieOptions +): string { + let cookie = `${name}=${encodeURIComponent(value)}`; + if (options?.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`; + if (options?.path) cookie += `; Path=${options.path}`; + if (options?.domain) cookie += `; Domain=${options.domain}`; + if (options?.secure) cookie += '; Secure'; + if (options?.httpOnly) cookie += '; HttpOnly'; + if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`; + return cookie; +} + +/** Appends a Set-Cookie value to the response without overwriting existing ones. */ +function appendSetCookie(res: ServerResponse, cookieStr: string): void { + const existing = res.getHeader('set-cookie'); + if (existing) { + const arr = Array.isArray(existing) ? existing : [String(existing)]; + res.setHeader('set-cookie', [...arr, cookieStr]); + } else { + res.setHeader('set-cookie', cookieStr); + } +} + +/** Reads and parses the request body based on its content type. Returns null for unsupported types. */ +async function parseBody( + event: H3Event, + contentType: string +): Promise { + const supportedTypes = [ + 'application/json', + 'application/ld+json', + 'application/x-www-form-urlencoded', + 'text/plain', + ]; + + if (!supportedTypes.some((t) => contentType.startsWith(t))) return null; + + const req = event.node?.req as IncomingMessage | undefined; + if (!req) return null; + + const rawBody = await new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + req.on('data', (chunk: Buffer) => chunks.push(chunk)); + req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); + req.on('error', reject); + }); + + if (!rawBody) return null; + + if ( + contentType.startsWith('application/json') || + contentType.startsWith('application/ld+json') + ) { + return JSON.parse(rawBody); + } + + return rawBody; +} /** * Nuxt adapter for the CSRF protection system. * - * Bridges H3 event objects with the framework-agnostic - * CSRF protection logic from `@csrf-armor/core`. + * Bridges H3 event objects with the framework-agnostic CSRF protection logic + * from `@csrf-armor/core` using only the H3Event's native properties + * (`event.method`, `event.headers`, `event.path`, `event.node`). */ export class NuxtAdapter implements CsrfAdapter { /** Cache parsed bodies to avoid double reads on the same event. */ @@ -31,44 +106,40 @@ export class NuxtAdapter implements CsrfAdapter { this.getTokenFromRequest = this.getTokenFromRequest.bind(this); } - /** - * Extracts a normalized CSRF request from an H3 event. - */ extractRequest(event: H3Event): CsrfRequest { - const rawCookies = parseCookies(event); - const cookies = new Map(); - for (const [name, value] of Object.entries(rawCookies)) { - cookies.set(name, value); - } - - const rawHeaders = getHeaders(event); - const headers = new Map(); - for (const [key, value] of Object.entries(rawHeaders)) { - if (value !== undefined) { - headers.set(key, String(value)); - } - } + const rawCookies = parseCookieHeader(event.headers.get('cookie')); + const cookies = new Map(Object.entries(rawCookies)); + + // Reconstruct the full URL from the H3Event's native properties + const host = + event.headers.get('x-forwarded-host') ?? + event.headers.get('host') ?? + 'localhost'; + const proto = + (event.headers.get('x-forwarded-proto') ?? 'http') + .split(',')[0] + ?.trim() ?? 'http'; + const path = event.path.startsWith('/') ? event.path : `/${event.path}`; return { - method: getMethod(event), - url: getRequestURL(event).href, - headers, + method: event.method, + url: new URL(path, `${proto}://${host}`).href, + headers: event.headers, // Web Headers API β€” accepted directly by core cookies, body: event, }; } - /** - * Applies CSRF response headers and cookies to the H3 event. - */ applyResponse(event: H3Event, csrfResponse: CsrfResponse): H3Event { + const res = event.node.res; + if (csrfResponse.headers instanceof Map) { for (const [key, value] of csrfResponse.headers) { - setResponseHeader(event, key, value); + res.setHeader(key, value); } } else { for (const [key, value] of Object.entries(csrfResponse.headers)) { - setResponseHeader(event, key, String(value)); + res.setHeader(key, String(value)); } } @@ -78,7 +149,7 @@ export class NuxtAdapter implements CsrfAdapter { value: string; options?: CookieOptions; }; - setCookie(event, name, value, this.adaptCookieOptions(options)); + appendSetCookie(res, serializeCookie(name, value, options)); } } else { for (const [name, cookieData] of Object.entries(csrfResponse.cookies)) { @@ -86,57 +157,40 @@ export class NuxtAdapter implements CsrfAdapter { value: string; options?: CookieOptions; }; - setCookie(event, name, value, this.adaptCookieOptions(options)); + appendSetCookie(res, serializeCookie(name, value, options)); } } return event; } - /** - * Extracts the CSRF token from the request header, cookie, or body. - * - * Priority: header > cookie > body (JSON/FormData/URL-encoded/text). - */ async getTokenFromRequest( request: CsrfRequest, config: RequiredCsrfConfig ): Promise { const event = request.body as H3Event; - // 1. Try header first (h3 normalizes header names to lowercase) - const headerValue = getHeader(event, config.token.headerName.toLowerCase()); + // 1. Try header first (H3 normalizes header names to lowercase) + const headerValue = event.headers.get( + config.token.headerName.toLowerCase() + ); if (headerValue) return headerValue; - // 2. Try cookie (lowercase to align with Express/Next.js adapters) - const cookies = parseCookies(event); - const cookieName = config.cookie.name.toLowerCase(); - const clientCookieValue = - cookies[cookieName] ?? cookies[config.cookie.name]; - if (clientCookieValue) return clientCookieValue; + // 2. Try cookie β€” use the already-parsed Map from extractRequest + const cookies = request.cookies as Map; + const cookieValue = + cookies.get(config.cookie.name.toLowerCase()) ?? + cookies.get(config.cookie.name); + if (cookieValue) return cookieValue; - // 3. Attempt to get parsed body from cache or parse it once + // 3. Try body let parsedBody: unknown; if (this.parsedBodyCache.has(event)) { parsedBody = this.parsedBodyCache.get(event); } else { - // Guard: h3 caches parsed bodies internally via _body on the node request. - // If the body was already read by other middleware, readBody returns the cached result. - // We still wrap in try/catch to handle edge cases where the stream was consumed externally. - const contentType = getHeader(event, 'content-type') ?? 'text/plain'; - const supportedTypes = [ - 'application/x-www-form-urlencoded', - 'multipart/form-data', - 'application/json', - 'application/ld+json', - 'text/plain', - ]; + const contentType = event.headers.get('content-type') ?? 'text/plain'; try { - if (supportedTypes.some((t) => contentType.startsWith(t))) { - parsedBody = await readBody(event); - } else { - parsedBody = null; - } + parsedBody = await parseBody(event, contentType); this.parsedBodyCache.set(event, parsedBody); } catch (error) { console.warn( @@ -149,17 +203,17 @@ export class NuxtAdapter implements CsrfAdapter { } // 4. Extract token from the parsed body - // h3's readBody returns plain objects for multipart/form-data (not FormData instances), - // so we handle both plain objects and string bodies uniformly. + if (parsedBody && typeof parsedBody === 'object') { - const jsonVal = (parsedBody as Record)[ + const val = (parsedBody as Record)[ config.token.fieldName ]; - if (typeof jsonVal === 'string') return jsonVal; + if (typeof val === 'string') return val; } else if (typeof parsedBody === 'string') { try { - const params = new URLSearchParams(parsedBody); - const tokenValue = params.get(config.token.fieldName); + const tokenValue = new URLSearchParams(parsedBody).get( + config.token.fieldName + ); if (tokenValue) return tokenValue; } catch (error) { console.warn( @@ -171,18 +225,4 @@ export class NuxtAdapter implements CsrfAdapter { return undefined; } - - /** Converts CookieOptions to h3-compatible cookie options. */ - private adaptCookieOptions(options?: CookieOptions): Record { - if (!options) return {}; - - return { - secure: options.secure, - httpOnly: options.httpOnly, - sameSite: options.sameSite, - path: options.path, - domain: options.domain, - maxAge: options.maxAge, - }; - } } diff --git a/packages/nuxt/src/runtime/server/middleware.ts b/packages/nuxt/src/runtime/server/middleware.ts index efc9130..079d071 100644 --- a/packages/nuxt/src/runtime/server/middleware.ts +++ b/packages/nuxt/src/runtime/server/middleware.ts @@ -1,9 +1,8 @@ -import type { H3Event } from 'h3'; -import { createError, defineEventHandler } from 'h3'; import { type CsrfConfig, createCsrfProtection } from '@csrf-armor/core'; -import { NuxtAdapter } from './adapter'; +import type { H3Event } from 'h3'; // @ts-expect-error - Nuxt auto-imports resolved at build time -import { useRuntimeConfig } from '#imports'; +import { defineEventHandler, useRuntimeConfig } from '#imports'; +import { NuxtAdapter } from './adapter'; /** * Lazily-initialized CSRF protection singleton. @@ -32,7 +31,7 @@ export default defineEventHandler(async (event: H3Event) => { const result = await csrfProtection.protect(event, event); if (!result.success) { - throw createError({ + throw Object.assign(new Error('CSRF validation failed'), { statusCode: 403, statusMessage: 'CSRF validation failed', data: { reason: result.reason }, diff --git a/packages/nuxt/test/adapter.test.ts b/packages/nuxt/test/adapter.test.ts index bed24a5..fb4d5ca 100644 --- a/packages/nuxt/test/adapter.test.ts +++ b/packages/nuxt/test/adapter.test.ts @@ -1,92 +1,151 @@ +import { Readable } from 'node:stream'; import type { CookieOptions, CsrfRequest, CsrfResponse, RequiredCsrfConfig, -} from '@csrf-armor/core' -import type { H3Event } from 'h3' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { NuxtAdapter } from '../src/runtime/server/adapter' - -vi.mock('h3', () => ({ - getMethod: vi.fn(), - getRequestURL: vi.fn(), - getHeaders: vi.fn(), - getHeader: vi.fn(), - parseCookies: vi.fn(), - setCookie: vi.fn(), - setResponseHeader: vi.fn(), - readBody: vi.fn(), -})) - -import { - getHeader, - getHeaders, - getMethod, - getRequestURL, - parseCookies, - readBody, - setCookie, - setResponseHeader, -} from 'h3' - -const mockedGetMethod = vi.mocked(getMethod) -const mockedGetRequestURL = vi.mocked(getRequestURL) -const mockedGetHeaders = vi.mocked(getHeaders) -const mockedGetHeader = vi.mocked(getHeader) -const mockedParseCookies = vi.mocked(parseCookies) -const mockedSetCookie = vi.mocked(setCookie) -const mockedSetResponseHeader = vi.mocked(setResponseHeader) -const mockedReadBody = vi.mocked(readBody) - -/** Creates a minimal H3Event mock for testing purposes. */ -function createMockEvent(overrides?: Record): H3Event { - return { ...overrides } as unknown as H3Event +} from '@csrf-armor/core'; +import type { H3Event } from 'h3'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { NuxtAdapter } from '../src/runtime/server/adapter'; + +interface MockServerResponse { + setHeader: ReturnType; + getHeader: ReturnType; + _headers: Record; +} + +interface MockH3Event { + method: string; + path: string; + headers: Headers; + node: { + req: Readable; + res: MockServerResponse; + }; + context: Record; +} + +/** Creates a mock Node.js readable stream that emits the given body then ends. */ +function createMockStream(body?: string | null): Readable { + const stream = new Readable({ read() {} }); + if (body) stream.push(body); + stream.push(null); + return stream; +} + +/** Creates a mock ServerResponse that tracks setHeader calls. */ +function createMockRes(): MockServerResponse { + const headers: Record = {}; + return { + setHeader: vi + .fn() + .mockImplementation((name: string, value: string | string[]) => { + headers[name.toLowerCase()] = value; + }), + getHeader: vi + .fn() + .mockImplementation((name: string) => headers[name.toLowerCase()]), + _headers: headers, + }; +} + +/** Creates a minimal H3Event mock with proper native properties. */ +function createMockEvent( + options: { + method?: string; + path?: string; + headers?: Record; + cookies?: Record; + body?: string | Record | null; + } = {} +): MockH3Event { + const headerInit: Record = { ...options.headers }; + + if (options.cookies && Object.keys(options.cookies).length > 0) { + headerInit.cookie = Object.entries(options.cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; '); + } + + const bodyStr = + options.body === null || options.body === undefined + ? null + : typeof options.body === 'string' + ? options.body + : JSON.stringify(options.body); + + return { + method: options.method ?? 'GET', + path: options.path ?? '/', + headers: new Headers(headerInit), + node: { + req: createMockStream(bodyStr), + res: createMockRes(), + }, + context: {}, + }; +} + +/** Extracts Set-Cookie values set on a mock response. */ +function getSetCookies(res: MockServerResponse): string[] { + const raw = res._headers['set-cookie']; + if (!raw) return []; + return Array.isArray(raw) ? raw : [raw]; +} + +/** Extracts response headers (excluding set-cookie) set on a mock response. */ +function getResponseHeaders(res: MockServerResponse): Record { + const result: Record = {}; + for (const [k, v] of Object.entries(res._headers)) { + if (k !== 'set-cookie') result[k] = Array.isArray(v) ? v.join(', ') : v; + } + return result; } describe('NuxtAdapter', () => { - let adapter: NuxtAdapter + let adapter: NuxtAdapter; beforeEach(() => { - vi.clearAllMocks() - adapter = new NuxtAdapter() - }) + adapter = new NuxtAdapter(); + }); describe('extractRequest', () => { it('should extract request data correctly', () => { - const mockEvent = createMockEvent() - - mockedGetMethod.mockReturnValue('POST') - mockedGetRequestURL.mockReturnValue(new URL('http://localhost/api')) - mockedGetHeaders.mockReturnValue({ - 'content-type': 'application/json', - 'x-csrf-token': 'test-token', - }) - mockedParseCookies.mockReturnValue({ - 'csrf-token': 'test-token', - 'session-id': 'test-session', - }) + const mockEvent = createMockEvent({ + method: 'POST', + path: '/api', + headers: { + host: 'localhost', + 'content-type': 'application/json', + 'x-csrf-token': 'test-token', + }, + cookies: { + 'csrf-token': 'test-token', + 'session-id': 'test-session', + }, + }); - const result = adapter.extractRequest(mockEvent) + const result = adapter.extractRequest(mockEvent as unknown as H3Event); - expect(result.method).toBe('POST') - expect(result.url).toBe('http://localhost/api') + expect(result.method).toBe('POST'); + expect(result.url).toBe('http://localhost/api'); - const headersMap = result.headers as Map - expect(headersMap.get('content-type')).toBe('application/json') - expect(headersMap.get('x-csrf-token')).toBe('test-token') + const headersMap = result.headers as Map; + expect(headersMap.get('content-type')).toBe('application/json'); + expect(headersMap.get('x-csrf-token')).toBe('test-token'); - const cookiesMap = result.cookies as Map - expect(cookiesMap.get('csrf-token')).toBe('test-token') - expect(cookiesMap.get('session-id')).toBe('test-session') + const cookiesMap = result.cookies as Map; + expect(cookiesMap.get('csrf-token')).toBe('test-token'); + expect(cookiesMap.get('session-id')).toBe('test-session'); - expect(result.body).toBe(mockEvent) - }) - }) + expect(result.body).toBe(mockEvent); + }); + }); describe('applyResponse', () => { it('should apply headers and cookies from Map correctly', () => { - const mockEvent = createMockEvent() + const mockEvent = createMockEvent(); const csrfResponse: CsrfResponse = { headers: new Map([ @@ -100,37 +159,37 @@ describe('NuxtAdapter', () => { { value: 'signed-token', options: { httpOnly: true, path: '/' } }, ], ]), - } - - const result = adapter.applyResponse(mockEvent, csrfResponse) - - expect(mockedSetResponseHeader).toHaveBeenCalledWith( - mockEvent, - 'x-csrf-token', - 'new-token', - ) - expect(mockedSetResponseHeader).toHaveBeenCalledWith( - mockEvent, - 'content-type', - 'application/json', - ) - expect(mockedSetCookie).toHaveBeenCalledWith( - mockEvent, - 'csrf-token', - 'new-token', - { httpOnly: true }, - ) - expect(mockedSetCookie).toHaveBeenCalledWith( - mockEvent, - 'csrf-token-server', - 'signed-token', - { httpOnly: true, path: '/' }, - ) - expect(result).toBe(mockEvent) - }) + }; + + const result = adapter.applyResponse( + mockEvent as unknown as H3Event, + csrfResponse + ); + + const headers = getResponseHeaders(mockEvent.node.res); + expect(headers['x-csrf-token']).toBe('new-token'); + expect(headers['content-type']).toBe('application/json'); + + const cookies = getSetCookies(mockEvent.node.res); + expect( + cookies.some( + (c) => c.startsWith('csrf-token=') && c.includes('HttpOnly') + ) + ).toBe(true); + expect( + cookies.some( + (c) => + c.startsWith('csrf-token-server=') && + c.includes('HttpOnly') && + c.includes('Path=/') + ) + ).toBe(true); + + expect(result).toBe(mockEvent); + }); it('should apply headers and cookies from objects correctly', () => { - const mockEvent = createMockEvent() + const mockEvent = createMockEvent(); const csrfResponse: CsrfResponse = { headers: { @@ -144,24 +203,26 @@ describe('NuxtAdapter', () => { options: { httpOnly: true, path: '/' }, }, }, - } - - const result = adapter.applyResponse(mockEvent, csrfResponse) - - expect(mockedSetResponseHeader).toHaveBeenCalledWith( - mockEvent, - 'x-csrf-token', - 'new-token', - ) - expect(mockedSetCookie).toHaveBeenCalledWith( - mockEvent, - 'csrf-token', - 'new-token', - { httpOnly: true }, - ) - expect(result).toBe(mockEvent) - }) - }) + }; + + const result = adapter.applyResponse( + mockEvent as unknown as H3Event, + csrfResponse + ); + + const headers = getResponseHeaders(mockEvent.node.res); + expect(headers['x-csrf-token']).toBe('new-token'); + + const cookies = getSetCookies(mockEvent.node.res); + expect( + cookies.some( + (c) => c.startsWith('csrf-token=') && c.includes('HttpOnly') + ) + ).toBe(true); + + expect(result).toBe(mockEvent); + }); + }); describe('getTokenFromRequest', () => { const baseConfig = { @@ -172,16 +233,13 @@ describe('NuxtAdapter', () => { cookie: { name: 'csrf-token', }, - } as RequiredCsrfConfig + } as RequiredCsrfConfig; it('should extract token from header', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'x-csrf-token') return 'header-token' - return undefined - }) - mockedParseCookies.mockReturnValue({}) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'x-csrf-token': 'header-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -189,24 +247,22 @@ describe('NuxtAdapter', () => { headers: new Map([['x-csrf-token', 'header-token']]), cookies: new Map(), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('header-token') - }) + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBe('header-token'); + }); it('should extract token from cookie with lowercased name lookup', async () => { - const mockEvent = createMockEvent() const configWithCasing = { ...baseConfig, cookie: { name: 'CSRF-Token' }, - } as RequiredCsrfConfig + } as RequiredCsrfConfig; - mockedGetHeader.mockReturnValue(undefined) - // parseCookies returns lowercase key (browser normalizes cookie names) - mockedParseCookies.mockReturnValue({ - 'csrf-token': 'cookie-token', - }) + const mockEvent = createMockEvent({ + method: 'POST', + cookies: { 'csrf-token': 'cookie-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -214,24 +270,25 @@ describe('NuxtAdapter', () => { headers: new Map(), cookies: new Map([['csrf-token', 'cookie-token']]), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, configWithCasing) - expect(token).toBe('cookie-token') - }) + const token = await adapter.getTokenFromRequest( + request, + configWithCasing + ); + expect(token).toBe('cookie-token'); + }); it('should fallback to original casing if lowercased cookie not found', async () => { - const mockEvent = createMockEvent() const configWithCasing = { ...baseConfig, cookie: { name: 'CSRF-Token' }, - } as RequiredCsrfConfig + } as RequiredCsrfConfig; - mockedGetHeader.mockReturnValue(undefined) - // parseCookies preserves original casing in this case - mockedParseCookies.mockReturnValue({ - 'CSRF-Token': 'cookie-token', - }) + const mockEvent = createMockEvent({ + method: 'POST', + cookies: { 'CSRF-Token': 'cookie-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -239,19 +296,20 @@ describe('NuxtAdapter', () => { headers: new Map(), cookies: new Map([['CSRF-Token', 'cookie-token']]), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, configWithCasing) - expect(token).toBe('cookie-token') - }) + const token = await adapter.getTokenFromRequest( + request, + configWithCasing + ); + expect(token).toBe('cookie-token'); + }); it('should extract token from cookie', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockReturnValue(undefined) - mockedParseCookies.mockReturnValue({ - 'csrf-token': 'cookie-token', - }) + const mockEvent = createMockEvent({ + method: 'POST', + cookies: { 'csrf-token': 'cookie-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -259,21 +317,18 @@ describe('NuxtAdapter', () => { headers: new Map(), cookies: new Map([['csrf-token', 'cookie-token']]), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('cookie-token') - }) + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBe('cookie-token'); + }); it('should extract token from JSON body', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') return 'application/json' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockResolvedValue({ csrf: 'body-token' }) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: { csrf: 'body-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -281,22 +336,18 @@ describe('NuxtAdapter', () => { headers: new Map([['content-type', 'application/json']]), cookies: new Map(), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('body-token') - }) + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBe('body-token'); + }); it('should extract token from URL-encoded body', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') - return 'application/x-www-form-urlencoded' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockResolvedValue({ csrf: 'form-token' }) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body: 'csrf=form-token&other=value', + }); const request: CsrfRequest = { method: 'POST', @@ -306,22 +357,21 @@ describe('NuxtAdapter', () => { ]), cookies: new Map(), body: mockEvent, - } - - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('form-token') - }) + }; - it('should extract token from multipart/form-data body (h3 returns plain object)', async () => { - const mockEvent = createMockEvent() + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBe('form-token'); + }); - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') return 'multipart/form-data' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - // h3's readBody returns a plain object for multipart/form-data, not FormData - mockedReadBody.mockResolvedValue({ csrf: 'formdata-token', other: 'value' }) + it('should not parse multipart/form-data body (token must be in header or cookie)', async () => { + // multipart/form-data is excluded from body parsing because the raw stream + // cannot be reliably parsed without a multipart parser. Callers must include + // the CSRF token in a header or cookie for multipart requests. + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'multipart/form-data' }, + body: 'csrf=formdata-token&other=value', + }); const request: CsrfRequest = { method: 'POST', @@ -329,21 +379,18 @@ describe('NuxtAdapter', () => { headers: new Map([['content-type', 'multipart/form-data']]), cookies: new Map(), body: mockEvent, - } - - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('formdata-token') - }) + }; - it('should extract token from URL-encoded string body', async () => { - const mockEvent = createMockEvent() + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBeUndefined(); + }); - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') return 'text/plain' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockResolvedValue('csrf=urlencoded-token&other=value') + it('should extract token from URL-encoded string body (text/plain)', async () => { + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'text/plain' }, + body: 'csrf=urlencoded-token&other=value', + }); const request: CsrfRequest = { method: 'POST', @@ -351,18 +398,18 @@ describe('NuxtAdapter', () => { headers: new Map([['content-type', 'text/plain']]), cookies: new Map(), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBe('urlencoded-token') - }) + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBe('urlencoded-token'); + }); it('should return undefined when no token is found', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockReturnValue(undefined) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockResolvedValue({}) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: {}, + }); const request: CsrfRequest = { method: 'POST', @@ -370,23 +417,21 @@ describe('NuxtAdapter', () => { headers: new Map(), cookies: new Map(), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBeUndefined() - }) + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBeUndefined(); + }); it('should handle readBody failure gracefully', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') return 'application/json' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockRejectedValue(new Error('Parse error')) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + }); - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + // Simulate a stream error + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + mockEvent.node.req.destroy(new Error('Stream error')); const request: CsrfRequest = { method: 'POST', @@ -394,24 +439,21 @@ describe('NuxtAdapter', () => { headers: new Map([['content-type', 'application/json']]), cookies: new Map(), body: mockEvent, - } + }; - const token = await adapter.getTokenFromRequest(request, baseConfig) - expect(token).toBeUndefined() - expect(warnSpy).toHaveBeenCalled() + const token = await adapter.getTokenFromRequest(request, baseConfig); + expect(token).toBeUndefined(); + expect(warnSpy).toHaveBeenCalled(); - warnSpy.mockRestore() - }) + warnSpy.mockRestore(); + }); it('should use cached body on second call for same event', async () => { - const mockEvent = createMockEvent() - - mockedGetHeader.mockImplementation((_event, name) => { - if (name === 'content-type') return 'application/json' - return undefined - }) - mockedParseCookies.mockReturnValue({}) - mockedReadBody.mockResolvedValue({ csrf: 'cached-token' }) + const mockEvent = createMockEvent({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: { csrf: 'cached-token' }, + }); const request: CsrfRequest = { method: 'POST', @@ -419,23 +461,24 @@ describe('NuxtAdapter', () => { headers: new Map([['content-type', 'application/json']]), cookies: new Map(), body: mockEvent, - } + }; // First call reads and caches - const token1 = await adapter.getTokenFromRequest(request, baseConfig) - expect(token1).toBe('cached-token') - expect(mockedReadBody).toHaveBeenCalledTimes(1) + const token1 = await adapter.getTokenFromRequest(request, baseConfig); + expect(token1).toBe('cached-token'); - // Second call uses cache, does not call readBody again - const token2 = await adapter.getTokenFromRequest(request, baseConfig) - expect(token2).toBe('cached-token') - expect(mockedReadBody).toHaveBeenCalledTimes(1) - }) - }) + // Mutate the stream so a second read would fail β€” proves cache is used + mockEvent.node.req.destroy(); + + // Second call uses the adapter's WeakMap cache + const token2 = await adapter.getTokenFromRequest(request, baseConfig); + expect(token2).toBe('cached-token'); + }); + }); describe('cookie options adaptation', () => { - it('should adapt cookie options correctly through applyResponse', () => { - const mockEvent = createMockEvent() + it('should serialize cookie options correctly through applyResponse', () => { + const mockEvent = createMockEvent(); const cookieOptions: CookieOptions = { secure: true, @@ -444,54 +487,52 @@ describe('NuxtAdapter', () => { path: '/api', domain: 'example.com', maxAge: 3600, - } + }; const csrfResponse: CsrfResponse = { headers: new Map(), cookies: new Map([ ['test-cookie', { value: 'test-value', options: cookieOptions }], ]), - } - - adapter.applyResponse(mockEvent, csrfResponse) - - expect(mockedSetCookie).toHaveBeenCalledWith( - mockEvent, - 'test-cookie', - 'test-value', - expect.objectContaining({ - secure: true, - httpOnly: true, - sameSite: 'strict', - path: '/api', - domain: 'example.com', - maxAge: 3600, - }), - ) - }) + }; + + adapter.applyResponse(mockEvent as unknown as H3Event, csrfResponse); + + const cookies = getSetCookies(mockEvent.node.res); + expect(cookies).toHaveLength(1); + const cookieStr = cookies[0] ?? ''; + expect(cookieStr).toContain('test-cookie=test-value'); + expect(cookieStr).toContain('Max-Age=3600'); + expect(cookieStr).toContain('Path=/api'); + expect(cookieStr).toContain('Domain=example.com'); + expect(cookieStr).toContain('Secure'); + expect(cookieStr).toContain('HttpOnly'); + expect(cookieStr).toContain('SameSite=strict'); + }); it('should handle undefined cookie options', () => { - const mockEvent = createMockEvent() + const mockEvent = createMockEvent(); const csrfResponse: CsrfResponse = { headers: new Map(), cookies: new Map([['test-cookie', { value: 'test-value' }]]), - } + }; - adapter.applyResponse(mockEvent, csrfResponse) + adapter.applyResponse(mockEvent as unknown as H3Event, csrfResponse); - expect(mockedSetCookie).toHaveBeenCalledWith( - mockEvent, - 'test-cookie', - 'test-value', - expect.objectContaining({}), - ) - }) - }) + const cookies = getSetCookies(mockEvent.node.res); + expect(cookies).toHaveLength(1); + expect(cookies[0]).toContain('test-cookie=test-value'); + }); + }); describe('concurrent requests', () => { it('should handle cookies and headers correctly for concurrent requests', async () => { - const events = Array.from({ length: 5 }, () => createMockEvent()) + const events = Array.from({ length: 5 }, (_, i) => + createMockEvent({ + headers: { 'x-request-id': `req-${i}` }, + }) + ); const csrfResponses: CsrfResponse[] = events.map((_, i) => ({ headers: new Map([ @@ -505,99 +546,91 @@ describe('NuxtAdapter', () => { { value: `session-${i}`, options: { secure: true, path: '/' } }, ], ]), - })) - - const promises = events.map((event, i) => - Promise.resolve(adapter.applyResponse(event, csrfResponses[i]!)), - ) - - const results = await Promise.all(promises) + })); + + const results = await Promise.all( + events.map((event, i) => + Promise.resolve( + adapter.applyResponse( + event as unknown as H3Event, + // biome-ignore lint/style/noNonNullAssertion: csrfResponses and events have equal length + csrfResponses[i]! + ) + ) + ) + ); results.forEach((result, i) => { - expect(result).toBe(events[i]) - }) + expect(result).toBe(events[i]); + }); events.forEach((event, i) => { - expect(mockedSetResponseHeader).toHaveBeenCalledWith( - event, - 'x-csrf-token', - `token-${i}`, - ) - expect(mockedSetCookie).toHaveBeenCalledWith( - event, - 'csrf-token', - `csrf-${i}`, - { httpOnly: true }, - ) - }) - }) + const headers = getResponseHeaders(event.node.res); + expect(headers['x-csrf-token']).toBe(`token-${i}`); + + const cookies = getSetCookies(event.node.res); + expect( + cookies.some( + (c) => + c.startsWith(`csrf-token=csrf-${i}`) && c.includes('HttpOnly') + ) + ).toBe(true); + }); + }); it('should maintain request isolation during concurrent token extraction from different sources', async () => { const config = { - token: { - headerName: 'x-csrf-token', - fieldName: 'csrf', - }, - cookie: { - name: 'csrf-token', - }, - } as RequiredCsrfConfig - - const headerEvent = createMockEvent() - const bodyEvent = createMockEvent() - const cookieEvent = createMockEvent() - - mockedGetHeader.mockImplementation((event, name) => { - if (event === headerEvent && name === 'x-csrf-token') - return 'header-token' - if (event === bodyEvent && name === 'content-type') - return 'application/json' - return undefined - }) - - mockedParseCookies.mockImplementation((event) => { - if (event === cookieEvent) return { 'csrf-token': 'cookie-token' } - return {} - }) - - mockedReadBody.mockImplementation(async (event) => { - if (event === bodyEvent) return { csrf: 'body-token' } - return {} - }) - - const headerRequest: CsrfRequest = { - method: 'POST', - url: 'http://localhost/api/1', - headers: new Map([['x-csrf-token', 'header-token']]), - cookies: new Map(), - body: headerEvent, - } - - const bodyRequest: CsrfRequest = { - method: 'POST', - url: 'http://localhost/api/2', - headers: new Map([['content-type', 'application/json']]), - cookies: new Map(), - body: bodyEvent, - } - - const cookieRequest: CsrfRequest = { - method: 'POST', - url: 'http://localhost/api/3', - headers: new Map(), - cookies: new Map([['csrf-token', 'cookie-token']]), - body: cookieEvent, - } + token: { headerName: 'x-csrf-token', fieldName: 'csrf' }, + cookie: { name: 'csrf-token' }, + } as RequiredCsrfConfig; + + const headerEvent = createMockEvent({ + headers: { 'x-csrf-token': 'header-token' }, + }); + const bodyEvent = createMockEvent({ + headers: { 'content-type': 'application/json' }, + body: { csrf: 'body-token' }, + }); + const cookieEvent = createMockEvent({ + cookies: { 'csrf-token': 'cookie-token' }, + }); const [headerToken, bodyToken, cookieToken] = await Promise.all([ - adapter.getTokenFromRequest(headerRequest, config), - adapter.getTokenFromRequest(bodyRequest, config), - adapter.getTokenFromRequest(cookieRequest, config), - ]) - - expect(headerToken).toBe('header-token') - expect(bodyToken).toBe('body-token') - expect(cookieToken).toBe('cookie-token') - }) - }) -}) + adapter.getTokenFromRequest( + { + method: 'POST', + url: 'http://localhost/api/1', + headers: new Map([['x-csrf-token', 'header-token']]), + cookies: new Map(), + body: headerEvent, + }, + config + ), + adapter.getTokenFromRequest( + { + method: 'POST', + url: 'http://localhost/api/2', + headers: new Map([['content-type', 'application/json']]), + cookies: new Map(), + body: bodyEvent, + }, + config + ), + adapter.getTokenFromRequest( + { + method: 'POST', + url: 'http://localhost/api/3', + headers: new Map(), + cookies: new Map([['csrf-token', 'cookie-token']]), + body: cookieEvent, + }, + config + ), + ]); + + expect(headerToken).toBe('header-token'); + expect(bodyToken).toBe('body-token'); + expect(cookieToken).toBe('cookie-token'); + }); + }); +}); diff --git a/packages/nuxt/test/middleware.test.ts b/packages/nuxt/test/middleware.test.ts index 18de1dc..7614efd 100644 --- a/packages/nuxt/test/middleware.test.ts +++ b/packages/nuxt/test/middleware.test.ts @@ -1,203 +1,231 @@ -import { createCsrfProtection, verifySignedToken } from '@csrf-armor/core' -import type { H3Event } from 'h3' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { NuxtAdapter } from '../src/runtime/server/adapter' - -vi.mock('h3', () => ({ - getMethod: vi.fn(), - getRequestURL: vi.fn(), - getHeaders: vi.fn(), - getHeader: vi.fn(), - parseCookies: vi.fn(), - setCookie: vi.fn(), - setResponseHeader: vi.fn(), - readBody: vi.fn(), -})) - -import { - getHeader, - getHeaders, - getMethod, - getRequestURL, - parseCookies, - readBody, - setCookie, - setResponseHeader, -} from 'h3' - -const mockedGetMethod = vi.mocked(getMethod) -const mockedGetRequestURL = vi.mocked(getRequestURL) -const mockedGetHeaders = vi.mocked(getHeaders) -const mockedGetHeader = vi.mocked(getHeader) -const mockedParseCookies = vi.mocked(parseCookies) -const mockedSetCookie = vi.mocked(setCookie) -const mockedReadBody = vi.mocked(readBody) -const mockedSetResponseHeader = vi.mocked(setResponseHeader) - -/** Creates an H3Event mock with configured h3 function behaviors. */ -function createGetEvent(url = 'http://localhost/', headers: Record = {}): H3Event { - const event = {} as unknown as H3Event - - mockedGetMethod.mockReturnValue('GET') - mockedGetRequestURL.mockReturnValue(new URL(url)) - mockedGetHeaders.mockReturnValue(headers) - mockedGetHeader.mockImplementation((_e, name) => headers[name as string]) - mockedParseCookies.mockReturnValue({}) - - return event +import { Readable } from 'node:stream'; +import { createCsrfProtection, verifySignedToken } from '@csrf-armor/core'; +import type { H3Event } from 'h3'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { NuxtAdapter } from '../src/runtime/server/adapter'; + +interface MockServerResponse { + setHeader: ReturnType; + getHeader: ReturnType; + _headers: Record; +} + +/** Creates a mock Node.js readable stream that emits body then ends. */ +function createMockStream(body?: string | null): Readable { + const stream = new Readable({ read() {} }); + if (body) stream.push(body); + stream.push(null); + return stream; +} + +/** Creates a mock ServerResponse that tracks setHeader calls. */ +function createMockRes(): MockServerResponse { + const headers: Record = {}; + return { + setHeader: vi + .fn() + .mockImplementation((name: string, value: string | string[]) => { + headers[name.toLowerCase()] = value; + }), + getHeader: vi + .fn() + .mockImplementation((name: string) => headers[name.toLowerCase()]), + _headers: headers, + }; +} + +function createGetEvent( + url = 'http://localhost/', + requestHeaders: Record = {} +): H3Event { + const parsedUrl = new URL(url); + const headers = new Headers({ + host: parsedUrl.host, + ...requestHeaders, + }); + return { + method: 'GET', + path: parsedUrl.pathname + parsedUrl.search, + headers, + node: { req: createMockStream(null), res: createMockRes() }, + context: {}, + } as unknown as H3Event; } function createPostEvent( url: string, - headers: Record, + requestHeaders: Record, cookies: Record, + body?: string | Record ): H3Event { - const event = {} as unknown as H3Event - - mockedGetMethod.mockReturnValue('POST') - mockedGetRequestURL.mockReturnValue(new URL(url)) - mockedGetHeaders.mockReturnValue(headers) - mockedGetHeader.mockImplementation((_e, name) => headers[name as string]) - mockedParseCookies.mockReturnValue(cookies) - - return event + const parsedUrl = new URL(url); + const cookieStr = Object.entries(cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; '); + + const headers = new Headers({ + host: parsedUrl.host, + ...requestHeaders, + ...(cookieStr ? { cookie: cookieStr } : {}), + }); + + const bodyStr = + body === undefined + ? null + : typeof body === 'string' + ? body + : JSON.stringify(body); + + return { + method: 'POST', + path: parsedUrl.pathname + parsedUrl.search, + headers, + node: { req: createMockStream(bodyStr), res: createMockRes() }, + context: {}, + } as unknown as H3Event; } -/** Collects cookies set via setCookie mock calls. */ -function getSetCookies(): Record { - const cookies: Record = {} - for (const call of mockedSetCookie.mock.calls) { - cookies[call[1] as string] = call[2] as string +/** Collects cookies set on the response. Returns nameβ†’value map. */ +function getSetCookies(event: H3Event): Record { + const res = (event.node.res as unknown as MockServerResponse)._headers[ + 'set-cookie' + ]; + const cookieHeaders = Array.isArray(res) ? res : res ? [String(res)] : []; + const result: Record = {}; + for (const cookieStr of cookieHeaders) { + const [nameValue] = cookieStr.split(';'); + if (!nameValue) continue; + const eqIdx = nameValue.indexOf('='); + if (eqIdx === -1) continue; + const name = nameValue.slice(0, eqIdx).trim(); + result[name] = decodeURIComponent(nameValue.slice(eqIdx + 1).trim()); } - return cookies + return result; } -/** Collects response headers set via setResponseHeader mock calls. */ -function getResponseHeaders(): Record { - const headers: Record = {} - for (const call of mockedSetResponseHeader.mock.calls) { - headers[call[1] as string] = call[2] as string +/** Collects non-cookie response headers. */ +function getResponseHeaders(event: H3Event): Record { + const all = (event.node.res as unknown as MockServerResponse)._headers; + const result: Record = {}; + for (const [k, v] of Object.entries(all)) { + if (k !== 'set-cookie') + result[k] = Array.isArray(v) ? v.join(', ') : String(v); } - return headers + return result; } describe('Nuxt CSRF Middleware Integration', () => { beforeEach(() => { - vi.clearAllMocks() - }) + vi.clearAllMocks(); + }); it('should allow GET requests without validation', async () => { - const adapter = new NuxtAdapter() - const protection = createCsrfProtection(adapter) - const event = createGetEvent() + const adapter = new NuxtAdapter(); + const protection = createCsrfProtection(adapter); + const event = createGetEvent(); - const result = await protection.protect(event, event) + const result = await protection.protect(event, event); - expect(result.success).toBe(true) - expect(result.token).toBeDefined() - }) + expect(result.success).toBe(true); + expect(result.token).toBeDefined(); + }); it('should set CSRF token header and cookie on GET', async () => { - const adapter = new NuxtAdapter() - const protection = createCsrfProtection(adapter) - const event = createGetEvent() + const adapter = new NuxtAdapter(); + const protection = createCsrfProtection(adapter); + const event = createGetEvent(); - await protection.protect(event, event) + await protection.protect(event, event); - const headers = getResponseHeaders() - const cookies = getSetCookies() + const headers = getResponseHeaders(event); + const cookies = getSetCookies(event); - expect(headers['x-csrf-token']).toBeDefined() - expect(cookies['csrf-token']).toBeDefined() - }) + expect(headers['x-csrf-token']).toBeDefined(); + expect(cookies['csrf-token']).toBeDefined(); + }); it('should exclude configured paths from protection', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { excludePaths: ['/api/webhook'], - }) + }); - mockedGetMethod.mockReturnValue('POST') - mockedGetRequestURL.mockReturnValue(new URL('http://localhost/api/webhook')) - mockedGetHeaders.mockReturnValue({}) - mockedGetHeader.mockReturnValue(undefined) - mockedParseCookies.mockReturnValue({}) + const event = createPostEvent('http://localhost/api/webhook', {}, {}); + const result = await protection.protect(event, event); - const event = {} as unknown as H3Event - const result = await protection.protect(event, event) - - expect(result.success).toBe(true) - }) + expect(result.success).toBe(true); + }); it('should use custom cookie name', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { cookie: { name: 'my-csrf' }, - }) + }); - const event = createGetEvent() - await protection.protect(event, event) + const event = createGetEvent(); + await protection.protect(event, event); - const cookies = getSetCookies() - expect(cookies['my-csrf']).toBeDefined() - }) + const cookies = getSetCookies(event); + expect(cookies['my-csrf']).toBeDefined(); + }); it('should generate unsigned tokens for double-submit strategy', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { strategy: 'double-submit', - }) + }); - const event = createGetEvent() - const result = await protection.protect(event, event) + const event = createGetEvent(); + await protection.protect(event, event); - const headers = getResponseHeaders() - const cookies = getSetCookies() + const headers = getResponseHeaders(event); + const cookies = getSetCookies(event); - expect(headers['x-csrf-token']).toBeDefined() - expect(cookies['csrf-token']).toBeDefined() - expect(headers['x-csrf-token']).toBe(cookies['csrf-token']) + expect(headers['x-csrf-token']).toBeDefined(); + expect(cookies['csrf-token']).toBeDefined(); + expect(headers['x-csrf-token']).toBe(cookies['csrf-token']); // Unsigned tokens have no dots - expect(headers['x-csrf-token']!.includes('.')).toBe(false) - }) + expect(headers['x-csrf-token']?.includes('.')).toBe(false); + }); it('should generate signed tokens for signed-double-submit strategy', async () => { - const secret = 'test-secret-32-characters-long-123' // gitleaks:allow - const adapter = new NuxtAdapter() + const secret = 'test-secret-32-characters-long-123'; // gitleaks:allow + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { strategy: 'signed-double-submit', secret, - }) + }); - const event = createGetEvent() - await protection.protect(event, event) + const event = createGetEvent(); + await protection.protect(event, event); - const headers = getResponseHeaders() - const cookies = getSetCookies() + const headers = getResponseHeaders(event); + const cookies = getSetCookies(event); - const headerToken = headers['x-csrf-token'] - const clientCookie = cookies['csrf-token'] - const serverCookie = cookies['csrf-token-server'] + const headerToken = headers['x-csrf-token']; + const clientCookie = cookies['csrf-token']; + const serverCookie = cookies['csrf-token-server']; - expect(headerToken).toBeDefined() - expect(clientCookie).toBeDefined() - expect(serverCookie).toBeDefined() + expect(headerToken).toBeDefined(); + expect(clientCookie).toBeDefined(); + expect(serverCookie).toBeDefined(); // Header and client cookie are unsigned and identical - expect(headerToken).toBe(clientCookie) - expect(headerToken!.includes('.')).toBe(false) + expect(headerToken).toBe(clientCookie); + expect(headerToken?.includes('.')).toBe(false); // Server cookie is signed (contains a dot) - expect(serverCookie!.split('.').length).toBe(2) + expect(serverCookie?.split('.').length).toBe(2); // Signed server cookie contains the unsigned token - const verified = await verifySignedToken(serverCookie!, secret) - expect(verified).toBe(headerToken) - }) + // biome-ignore lint/style/noNonNullAssertion: serverCookie is already assigned + const verified = await verifySignedToken(serverCookie!, secret); + expect(verified).toBe(headerToken); + }); it('should validate origin-check POST with correct origin', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); vi.spyOn(adapter, 'extractRequest').mockImplementation(() => ({ method: 'POST', @@ -205,21 +233,21 @@ describe('Nuxt CSRF Middleware Integration', () => { headers: new Map([['origin', 'http://localhost']]), cookies: new Map(), body: undefined, - })) + })); const protection = createCsrfProtection(adapter, { strategy: 'origin-check', allowedOrigins: ['http://localhost'], - }) + }); - const event = {} as unknown as H3Event - const result = await protection.protect(event, event) + const event = createPostEvent('http://localhost/api', {}, {}); + const result = await protection.protect(event, event); - expect(result.success).toBe(true) - }) + expect(result.success).toBe(true); + }); it('should reject origin-check POST with wrong origin', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); vi.spyOn(adapter, 'extractRequest').mockImplementation(() => ({ method: 'POST', @@ -227,84 +255,83 @@ describe('Nuxt CSRF Middleware Integration', () => { headers: new Map([['origin', 'http://evil.com']]), cookies: new Map(), body: undefined, - })) + })); const protection = createCsrfProtection(adapter, { strategy: 'origin-check', allowedOrigins: ['http://localhost'], - }) + }); - const event = {} as unknown as H3Event - const result = await protection.protect(event, event) + const event = createPostEvent('http://localhost/api', {}, {}); + const result = await protection.protect(event, event); - expect(result.success).toBe(false) - }) + expect(result.success).toBe(false); + }); it('should accept POST with matching token for double-submit', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { strategy: 'double-submit', - }) + }); // Issue a token via GET - const getEvent = createGetEvent() - await protection.protect(getEvent, getEvent) - const issuedToken = getResponseHeaders()['x-csrf-token']! + const getEvent = createGetEvent(); + await protection.protect(getEvent, getEvent); + const issuedToken = getResponseHeaders(getEvent)['x-csrf-token']; - vi.clearAllMocks() + expect(issuedToken).toBeDefined(); // Submit it back in header + cookie const postEvent = createPostEvent( 'http://localhost/api', { 'x-csrf-token': issuedToken }, - { 'csrf-token': issuedToken }, - ) - const result = await protection.protect(postEvent, postEvent) + { 'csrf-token': issuedToken } + ); + const result = await protection.protect(postEvent, postEvent); - expect(result.success).toBe(true) - }) + expect(result.success).toBe(true); + }); it('should accept POST with token in JSON body for double-submit', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { strategy: 'double-submit', token: { fieldName: '_csrf' }, - }) + }); // Issue a token via GET - const getEvent = createGetEvent() - await protection.protect(getEvent, getEvent) - const issuedToken = getResponseHeaders()['x-csrf-token']! + const getEvent = createGetEvent(); + await protection.protect(getEvent, getEvent); + const issuedToken = getResponseHeaders(getEvent)['x-csrf-token']; - vi.clearAllMocks() + expect(issuedToken).toBeDefined(); // Submit token in body + cookie (no header) const postEvent = createPostEvent( 'http://localhost/api', { 'content-type': 'application/json' }, { 'csrf-token': issuedToken }, - ) - mockedReadBody.mockResolvedValue({ _csrf: issuedToken }) - - const result = await protection.protect(postEvent, postEvent) + { _csrf: issuedToken } + ); + const result = await protection.protect(postEvent, postEvent); - expect(result.success).toBe(true) - }) + expect(result.success).toBe(true); + }); it('should reject POST without token for double-submit', async () => { - const adapter = new NuxtAdapter() + const adapter = new NuxtAdapter(); const protection = createCsrfProtection(adapter, { strategy: 'double-submit', - }) + }); const event = createPostEvent( 'http://localhost/api', { 'content-type': 'application/json' }, - {}, - ) + {} + ); - const result = await protection.protect(event, event) + const result = await protection.protect(event, event); - expect(result.success).toBe(false) - }) -}) + expect(result.success).toBe(false); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ee467a..ccf7729 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,16 @@ overrides: brace-expansion: ^2.0.2 tmp: '>=0.2.4' vite: ^6.4.1 - qs: '>=6.14.1' + qs: '>=6.14.2' + simple-git: '>=3.32.3' + rollup: '>=4.59.0' + serialize-javascript: '>=7.0.3' + svgo: '>=4.0.1' + tar: '>=7.5.11' + devalue: '>=5.6.3' + minimatch@>=5.0.0 <6.0.0: 5.1.9 + minimatch@>=9.0.0 <10.0.0: 9.0.9 + minimatch@>=10.0.0 <11.0.0: 10.2.4 importers: @@ -61,7 +70,7 @@ importers: version: link:../../packages/nuxt nuxt: specifier: ^4.3.1 - version: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) + version: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) vue: specifier: ^3.5.0 version: 3.5.28(typescript@5.9.3) @@ -137,15 +146,12 @@ importers: '@nuxt/schema': specifier: ^4.3.1 version: 4.3.1 - defu: - specifier: ^6.1.4 - version: 6.1.4 h3: specifier: ^1.15.3 version: 1.15.5 nuxt: specifier: ^4.3.1 - version: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) + version: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) tsdown: specifier: ^0.20.1 version: 0.20.1(typescript@5.9.3) @@ -1712,7 +1718,7 @@ packages: resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} engines: {node: '>=20.19.0'} peerDependencies: - rollup: '>=4.0.0' + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1721,7 +1727,7 @@ packages: resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1730,7 +1736,7 @@ packages: resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1739,7 +1745,7 @@ packages: resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1748,7 +1754,7 @@ packages: resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1757,7 +1763,7 @@ packages: resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1766,7 +1772,7 @@ packages: resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true @@ -1775,133 +1781,133 @@ packages: resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.59.0' peerDependenciesMeta: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.57.1': - resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.57.1': - resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.57.1': - resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.57.1': - resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.57.1': - resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.57.1': - resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': - resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.57.1': - resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.57.1': - resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.57.1': - resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.57.1': - resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.57.1': - resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.57.1': - resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.57.1': - resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.57.1': - resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.57.1': - resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.57.1': - resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.57.1': - resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.57.1': - resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.57.1': - resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.57.1': - resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.57.1': - resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.57.1': - resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.57.1': - resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.57.1': - resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -2717,8 +2723,8 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.6.2: - resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} diff@8.0.3: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} @@ -3488,16 +3494,16 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@10.2.1: - resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} - engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minipass@7.1.2: @@ -4010,8 +4016,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -4026,9 +4032,6 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -4150,15 +4153,15 @@ packages: hasBin: true peerDependencies: rolldown: 1.x || ^1.0.0-beta - rollup: 2.x || 3.x || 4.x + rollup: '>=4.59.0' peerDependenciesMeta: rolldown: optional: true rollup: optional: true - rollup@4.57.1: - resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4181,8 +4184,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.4.4: - resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + sax@1.5.0: + resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==} engines: {node: '>=11.0.0'} saxes@6.0.0: @@ -4217,8 +4220,9 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serialize-javascript@7.0.4: + resolution: {integrity: sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==} + engines: {node: '>=20.0.0'} seroval@1.5.0: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} @@ -4277,8 +4281,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-git@3.31.1: - resolution: {integrity: sha512-oiWP4Q9+kO8q9hHqkX35uuHmxiEbZNTrZ5IPxgMGrJwN76pzjm/jabkZO0ItEcqxAincqGAzL3QHSaHt4+knBg==} + simple-git@3.33.0: + resolution: {integrity: sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==} sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} @@ -4424,8 +4428,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svgo@4.0.0: - resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} engines: {node: '>=16'} hasBin: true @@ -4443,8 +4447,8 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.9: - resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} + tar@7.5.11: + resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} engines: {node: '>=18'} term-size@2.2.1: @@ -5825,7 +5829,7 @@ snapshots: node-fetch: 2.7.0 nopt: 8.1.0 semver: 7.7.4 - tar: 7.5.9 + tar: 7.5.11 transitivePeerDependencies: - encoding - supports-color @@ -5960,7 +5964,7 @@ snapshots: perfect-debounce: 2.1.0 pkg-types: 2.3.0 semver: 7.7.4 - simple-git: 3.31.1 + simple-git: 3.33.0 sirv: 3.0.2 structured-clone-es: 1.0.0 tinyglobby: 0.2.15 @@ -6000,7 +6004,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': + '@nuxt/nitro-server@4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 4.3.1(magicast@0.5.2) @@ -6009,7 +6013,7 @@ snapshots: consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.2 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 @@ -6018,7 +6022,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.1(rolldown@1.0.0-rc.1) - nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) + nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 @@ -6065,7 +6069,7 @@ snapshots: - uploadthing - xml2js - '@nuxt/nitro-server@4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': + '@nuxt/nitro-server@4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 4.3.1(magicast@0.5.2) @@ -6074,7 +6078,7 @@ snapshots: consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.2 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 @@ -6083,7 +6087,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.1(rolldown@1.0.0-rc.1) - nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) + nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 @@ -6147,10 +6151,10 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/vite-builder@4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2)': + '@nuxt/vite-builder@4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2)': dependencies: '@nuxt/kit': 4.3.1(magicast@0.5.2) - '@rollup/plugin-replace': 6.0.3(rollup@4.57.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) '@vitejs/plugin-vue': 6.0.4(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.4(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) autoprefixer: 10.4.24(postcss@8.5.6) @@ -6166,11 +6170,11 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) + nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.57.1) + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.59.0) seroval: 1.5.0 std-env: 3.10.0 ufo: 1.6.3 @@ -6207,10 +6211,10 @@ snapshots: - vue-tsc - yaml - '@nuxt/vite-builder@4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2)': + '@nuxt/vite-builder@4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2)': dependencies: '@nuxt/kit': 4.3.1(magicast@0.5.2) - '@rollup/plugin-replace': 6.0.3(rollup@4.57.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) '@vitejs/plugin-vue': 6.0.4(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.4(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) autoprefixer: 10.4.24(postcss@8.5.6) @@ -6226,11 +6230,11 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) + nuxt: 4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.57.1) + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.59.0) seroval: 1.5.0 std-env: 3.10.0 ufo: 1.6.3 @@ -6590,13 +6594,13 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.4': {} - '@rollup/plugin-alias@6.0.0(rollup@4.57.1)': + '@rollup/plugin-alias@6.0.0(rollup@4.59.0)': optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)': + '@rollup/plugin-commonjs@29.0.0(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) commondir: 1.0.1 estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.3) @@ -6604,128 +6608,128 @@ snapshots: magic-string: 0.30.21 picomatch: 4.0.3 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-inject@5.0.5(rollup@4.57.1)': + '@rollup/plugin-inject@5.0.5(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-json@6.1.0(rollup@4.57.1)': + '@rollup/plugin-json@6.1.0(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.57.1)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-replace@6.0.3(rollup@4.57.1)': + '@rollup/plugin-replace@6.0.3(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) magic-string: 0.30.21 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/plugin-terser@0.4.4(rollup@4.57.1)': + '@rollup/plugin-terser@0.4.4(rollup@4.59.0)': dependencies: - serialize-javascript: 6.0.2 + serialize-javascript: 7.0.4 smob: 1.6.1 terser: 5.46.0 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/pluginutils@5.3.0(rollup@4.57.1)': + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.57.1 + rollup: 4.59.0 - '@rollup/rollup-android-arm-eabi@4.57.1': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.57.1': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.57.1': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.57.1': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.57.1': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.57.1': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.57.1': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.57.1': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.57.1': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.57.1': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.57.1': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.57.1': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.57.1': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.57.1': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.57.1': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.57.1': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.57.1': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.57.1': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.57.1': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.57.1': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.57.1': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.57.1': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.57.1': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.57.1': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@sindresorhus/is@7.2.0': {} @@ -6855,10 +6859,10 @@ snapshots: unhead: 2.1.4 vue: 3.5.28(typescript@5.9.3) - '@vercel/nft@1.3.1(rollup@4.57.1)': + '@vercel/nft@1.3.1(rollup@4.59.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3 - '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 @@ -7087,7 +7091,7 @@ snapshots: '@vue/compiler-vue2': 2.7.16 '@vue/shared': 3.5.28 alien-signals: 1.0.13 - minimatch: 9.0.5 + minimatch: 9.0.9 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: @@ -7285,7 +7289,7 @@ snapshots: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.0 raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 @@ -7626,7 +7630,7 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.6.2: {} + devalue@5.6.3: {} diff@8.0.3: {} @@ -7838,7 +7842,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.0 @@ -7874,7 +7878,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.0 @@ -8021,14 +8025,14 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 9.0.9 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@13.0.5: dependencies: - minimatch: 10.2.1 + minimatch: 10.2.4 minipass: 7.1.2 path-scurry: 2.0.1 @@ -8471,15 +8475,15 @@ snapshots: min-indent@1.0.1: {} - minimatch@10.2.1: + minimatch@10.2.4: dependencies: brace-expansion: 2.0.2 - minimatch@5.1.6: + minimatch@5.1.9: dependencies: brace-expansion: 2.0.2 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 @@ -8545,14 +8549,14 @@ snapshots: nitropack@2.13.1(rolldown@1.0.0-rc.1): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@rollup/plugin-alias': 6.0.0(rollup@4.57.1) - '@rollup/plugin-commonjs': 29.0.0(rollup@4.57.1) - '@rollup/plugin-inject': 5.0.5(rollup@4.57.1) - '@rollup/plugin-json': 6.1.0(rollup@4.57.1) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.1) - '@rollup/plugin-replace': 6.0.3(rollup@4.57.1) - '@rollup/plugin-terser': 0.4.4(rollup@4.57.1) - '@vercel/nft': 1.3.1(rollup@4.57.1) + '@rollup/plugin-alias': 6.0.0(rollup@4.59.0) + '@rollup/plugin-commonjs': 29.0.0(rollup@4.59.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.59.0) + '@rollup/plugin-json': 6.1.0(rollup@4.59.0) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0) + '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) + '@rollup/plugin-terser': 0.4.4(rollup@4.59.0) + '@vercel/nft': 1.3.1(rollup@4.59.0) archiver: 7.0.1 c12: 3.3.3(magicast@0.5.2) chokidar: 5.0.0 @@ -8594,8 +8598,8 @@ snapshots: pkg-types: 2.3.0 pretty-bytes: 7.1.0 radix3: 1.1.2 - rollup: 4.57.1 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.57.1) + rollup: 4.59.0 + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-rc.1)(rollup@4.59.0) scule: 1.3.0 semver: 7.7.4 serve-placeholder: 2.0.2 @@ -8679,16 +8683,16 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2): + nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2): dependencies: '@dxup/nuxt': 0.3.2(magicast@0.5.2) '@nuxt/cli': 3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.2) '@nuxt/devtools': 3.2.1(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) '@nuxt/kit': 4.3.1(magicast@0.5.2) - '@nuxt/nitro-server': 4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) + '@nuxt/nitro-server': 4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) '@nuxt/schema': 4.3.1 '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.3.1(magicast@0.5.2)) - '@nuxt/vite-builder': 4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2) + '@nuxt/vite-builder': 4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@2.2.12(typescript@5.9.3))(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2) '@unhead/vue': 2.1.4(vue@3.5.28(typescript@5.9.3)) '@vue/shared': 3.5.28 c12: 3.3.3(magicast@0.5.2) @@ -8698,7 +8702,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.2 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 @@ -8802,16 +8806,16 @@ snapshots: - xml2js - yaml - nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2): + nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2): dependencies: '@dxup/nuxt': 0.3.2(magicast@0.5.2) '@nuxt/cli': 3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.2) '@nuxt/devtools': 3.2.1(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.28(typescript@5.9.3)) '@nuxt/kit': 4.3.1(magicast@0.5.2) - '@nuxt/nitro-server': 4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) + '@nuxt/nitro-server': 4.3.1(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3) '@nuxt/schema': 4.3.1 '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.3.1(magicast@0.5.2)) - '@nuxt/vite-builder': 4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.57.1)(terser@5.46.0)(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2) + '@nuxt/vite-builder': 4.3.1(@biomejs/biome@2.3.13)(@types/node@20.19.30)(magicast@0.5.2)(nuxt@4.3.1(@biomejs/biome@2.3.13)(@parcel/watcher@2.5.6)(@types/node@20.19.30)(@vue/compiler-sfc@3.5.28)(cac@6.7.14)(db0@0.3.4)(ioredis@5.9.3)(magicast@0.5.2)(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.30)(jiti@2.6.1)(terser@5.46.0)(yaml@2.8.2))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(rollup@4.59.0)(terser@5.46.0)(typescript@5.9.3)(vue@3.5.28(typescript@5.9.3))(yaml@2.8.2) '@unhead/vue': 2.1.4(vue@3.5.28(typescript@5.9.3)) '@vue/shared': 3.5.28 c12: 3.3.3(magicast@0.5.2) @@ -8821,7 +8825,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.2 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 @@ -9271,7 +9275,7 @@ snapshots: dependencies: postcss: 8.5.6 postcss-value-parser: 4.2.0 - svgo: 4.0.0 + svgo: 4.0.1 postcss-unique-selectors@7.0.4(postcss@8.5.6): dependencies: @@ -9318,7 +9322,7 @@ snapshots: punycode@2.3.1: {} - qs@6.14.1: + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -9330,10 +9334,6 @@ snapshots: radix3@1.1.2: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - range-parser@1.2.1: {} raw-body@2.5.2: @@ -9389,7 +9389,7 @@ snapshots: readdir-glob@1.1.3: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 readdirp@4.1.2: {} @@ -9477,7 +9477,7 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 - rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-rc.1)(rollup@4.57.1): + rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-rc.1)(rollup@4.59.0): dependencies: open: 8.4.2 picomatch: 4.0.3 @@ -9485,37 +9485,37 @@ snapshots: yargs: 17.7.2 optionalDependencies: rolldown: 1.0.0-rc.1 - rollup: 4.57.1 + rollup: 4.59.0 - rollup@4.57.1: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.1 - '@rollup/rollup-android-arm64': 4.57.1 - '@rollup/rollup-darwin-arm64': 4.57.1 - '@rollup/rollup-darwin-x64': 4.57.1 - '@rollup/rollup-freebsd-arm64': 4.57.1 - '@rollup/rollup-freebsd-x64': 4.57.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 - '@rollup/rollup-linux-arm-musleabihf': 4.57.1 - '@rollup/rollup-linux-arm64-gnu': 4.57.1 - '@rollup/rollup-linux-arm64-musl': 4.57.1 - '@rollup/rollup-linux-loong64-gnu': 4.57.1 - '@rollup/rollup-linux-loong64-musl': 4.57.1 - '@rollup/rollup-linux-ppc64-gnu': 4.57.1 - '@rollup/rollup-linux-ppc64-musl': 4.57.1 - '@rollup/rollup-linux-riscv64-gnu': 4.57.1 - '@rollup/rollup-linux-riscv64-musl': 4.57.1 - '@rollup/rollup-linux-s390x-gnu': 4.57.1 - '@rollup/rollup-linux-x64-gnu': 4.57.1 - '@rollup/rollup-linux-x64-musl': 4.57.1 - '@rollup/rollup-openbsd-x64': 4.57.1 - '@rollup/rollup-openharmony-arm64': 4.57.1 - '@rollup/rollup-win32-arm64-msvc': 4.57.1 - '@rollup/rollup-win32-ia32-msvc': 4.57.1 - '@rollup/rollup-win32-x64-gnu': 4.57.1 - '@rollup/rollup-win32-x64-msvc': 4.57.1 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 rou3@0.7.12: {} @@ -9532,7 +9532,7 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.4.4: {} + sax@1.5.0: {} saxes@6.0.0: dependencies: @@ -9582,9 +9582,7 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 + serialize-javascript@7.0.4: {} seroval@1.5.0: {} @@ -9684,7 +9682,7 @@ snapshots: signal-exit@4.1.0: {} - simple-git@3.31.1: + simple-git@3.33.0: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 @@ -9812,7 +9810,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svgo@4.0.0: + svgo@4.0.1: dependencies: commander: 11.1.0 css-select: 5.2.2 @@ -9820,7 +9818,7 @@ snapshots: css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.4 + sax: 1.5.0 symbol-tree@3.2.4: {} @@ -9837,7 +9835,7 @@ snapshots: - bare-abort-controller - react-native-b4a - tar@7.5.9: + tar@7.5.11: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -10205,7 +10203,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.57.1 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.30