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
+
+
+
+[](https://github.com/muneebs/csrf-armor/actions/workflows/ci.yml)
+[](https://badge.fury.io/js/@csrf-armor%2Fnuxt)
+[](https://opensource.org/licenses/MIT)
+[](https://www.typescriptlang.org/)
+[](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