diff --git a/build/pre-deploy-lib.mjs b/build/pre-deploy-lib.mjs new file mode 100644 index 0000000..cad04ae --- /dev/null +++ b/build/pre-deploy-lib.mjs @@ -0,0 +1,260 @@ +/** + * pre-deploy-lib.mjs — Pure functions extracted from pre-deploy.mjs for testability. + * + * All functions are side-effect-free: they throw on fatal errors (instead of + * calling process.exit) and accept an `onWarn` callback (instead of printing + * directly). The caller in pre-deploy.mjs wires these to fatal()/warn(). + */ + +import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser"; + +// ── Env Refs ───────────────────────────────────────────────────────────────── + +/** + * Resolve ${VAR} and ${VAR:-default} references in text, skipping YAML comment lines. + * @param {string} text - Input text with ${VAR} references + * @param {Record} env - Environment variables + * @param {(msg: string) => void} [onWarn] - Warning callback for unresolved vars + * @returns {string} Resolved text + */ +export function resolveEnvRefs(text, env, onWarn) { + return text.split("\n").map((line) => { + if (line.trimStart().startsWith("#")) return line; + + return line.replace(/\$\{([^}]+)\}/g, (_match, expr) => { + const defaultMatch = expr.match(/^([^:]+):-(.*)$/); + if (defaultMatch) { + const key = defaultMatch[1]; + const defaultVal = defaultMatch[2]; + return env[key] !== undefined && env[key] !== "" ? env[key] : defaultVal; + } + const value = env[expr]; + if (value === undefined) { + if (onWarn) onWarn(`Unresolved env var: \${${expr}} (will be empty string)`); + return ""; + } + return value; + }); + }).join("\n"); +} + +// ── Deep Merge ─────────────────────────────────────────────────────────────── + +export function isPlainObject(val) { + return typeof val === "object" && val !== null && !Array.isArray(val); +} + +/** Deep merge source into target. Source values win at any depth. */ +export function deepMerge(target, source) { + const result = { ...target }; + for (const key of Object.keys(source)) { + const srcVal = source[key]; + const tgtVal = result[key]; + if (isPlainObject(srcVal) && isPlainObject(tgtVal)) { + result[key] = deepMerge(tgtVal, srcVal); + } else { + result[key] = srcVal; + } + } + return result; +} + +// ── Memory Parsing ─────────────────────────────────────────────────────────── + +export function parseMemoryValue(val) { + const str = String(val).trim(); + const match = str.match(/^(\d+(?:\.\d+)?)\s*(G|M|GB|MB|g|m|gb|mb)?$/i); + if (!match) throw new Error(`Invalid memory value: ${val}`); + const num = parseFloat(match[1]); + const unit = (match[2] || "M").toUpperCase().replace("B", ""); + const mb = unit === "G" ? Math.floor(num * 1024) : Math.floor(num); + return { mb, original: str }; +} + +export function formatMemory(mb) { + if (mb >= 1024 && mb % 1024 === 0) return `${mb / 1024}G`; + return `${mb}M`; +} + +// ── JSONC Parsing ──────────────────────────────────────────────────────────── + +export function parseJsoncFile(text, filePath) { + const errors = []; + const result = parseJsonc(text, errors, { allowTrailingComma: true }); + if (errors.length > 0) { + const errorMsgs = errors.map(e => ` offset ${e.offset}: ${printParseErrorCode(e.error)}`).join("\n"); + throw new Error(`JSONC parse errors in ${filePath}:\n${errorMsgs}`); + } + return result; +} + +// ── Claw Validation ────────────────────────────────────────────────────────── + +export function validateClaw(name, claw, onWarn) { + const required = ["domain", "gateway_port", "dashboard_port"]; + for (const field of required) { + if (claw[field] === undefined || claw[field] === "") { + throw new Error(`Claw '${name}' is missing required field: ${field}`); + } + } + + const telegram = claw.telegram; + if (!telegram?.bot_token) { + if (onWarn) onWarn(`Claw '${name}' has no telegram.bot_token — Telegram will be disabled`); + } +} + +// ── Daily Report Time ──────────────────────────────────────────────────────── + +export const TZ_ABBREVIATIONS = { + PST: "America/Los_Angeles", PDT: "America/Los_Angeles", + EST: "America/New_York", EDT: "America/New_York", + CST: "America/Chicago", CDT: "America/Chicago", + MST: "America/Denver", MDT: "America/Denver", + UTC: "UTC", GMT: "Europe/London", + CET: "Europe/Berlin", CEST: "Europe/Berlin", +}; + +export function parseDailyReportTime(timeStr, onWarn) { + const fallback = { cronExpr: "30 9 * * *", ianaTz: "America/Los_Angeles" }; + if (!timeStr) return fallback; + + const match = String(timeStr).match(/^(\d{1,2}):(\d{2})\s*(AM|PM)\s*(\w+)$/i); + if (!match) { + if (onWarn) onWarn(`Could not parse daily_report time "${timeStr}" — using default 9:30 AM PST`); + return fallback; + } + + let hour = parseInt(match[1], 10); + const minute = parseInt(match[2], 10); + const ampm = match[3].toUpperCase(); + const tzAbbr = match[4].toUpperCase(); + + if (ampm === "PM" && hour !== 12) hour += 12; + if (ampm === "AM" && hour === 12) hour = 0; + + const ianaTz = TZ_ABBREVIATIONS[tzAbbr]; + if (!ianaTz) { + if (onWarn) onWarn(`Unknown timezone abbreviation "${match[4]}" — using America/Los_Angeles`); + return { cronExpr: `${minute} ${hour} * * *`, ianaTz: "America/Los_Angeles" }; + } + + return { cronExpr: `${minute} ${hour} * * *`, ianaTz }; +} + +// ── Env Value Formatting ───────────────────────────────────────────────────── + +export function formatEnvValue(val) { + const s = String(val ?? ""); + if (s === "") return ""; + if (/[\s'"\\$`!#&|;()<>{}]/.test(s)) { + return "'" + s.replace(/'/g, "'\\''") + "'"; + } + return s; +} + +// ── Stack Env Generation ───────────────────────────────────────────────────── + +export function generateStackEnv(env, config, claws) { + const lines = [ + "# Generated by pre-deploy — DO NOT EDIT", + "# To regenerate: npm run pre-deploy", + "", + ]; + + // Source: .env + const envVars = [ + "VPS_IP", "SSH_KEY", "SSH_PORT", "SSH_USER", + "HOSTALERT_TELEGRAM_BOT_TOKEN", "HOSTALERT_TELEGRAM_CHAT_ID", + "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_TUNNEL_TOKEN", + ]; + lines.push("# Source: .env"); + for (const key of envVars) { + if (env[key] !== undefined) { + lines.push(`ENV__${key}=${formatEnvValue(env[key])}`); + } + } + lines.push(""); + + const stack = config.stack || {}; + const host = config.host || {}; + + // Source: stack.yml → host + lines.push("# Source: stack.yml → host"); + if (host.hostname) lines.push(`STACK__HOST__HOSTNAME=${formatEnvValue(host.hostname)}`); + lines.push(""); + + // Source: stack.yml → stack + const installDir = String(stack.install_dir || "/home/openclaw"); + const projectName = String(stack.project_name || "openclaw-stack"); + lines.push("# Source: stack.yml → stack"); + lines.push(`STACK__STACK__INSTALL_DIR=${formatEnvValue(installDir)}`); + lines.push(`STACK__STACK__PROJECT_NAME=${formatEnvValue(projectName)}`); + lines.push(`STACK__STACK__LOGGING__VECTOR=${stack.logging?.vector ?? false}`); + if (stack.openclaw?.version) { + lines.push(`STACK__STACK__OPENCLAW__VERSION=${formatEnvValue(stack.openclaw.version)}`); + } + if (stack.openclaw?.source) { + lines.push(`STACK__STACK__OPENCLAW__SOURCE=${formatEnvValue(stack.openclaw.source)}`); + } + if (stack.sandbox_toolkit) { + lines.push(`STACK__STACK__SANDBOX_TOOLKIT=${formatEnvValue(stack.sandbox_toolkit)}`); + } + if (stack.sandbox_registry) { + lines.push(`STACK__STACK__SANDBOX_REGISTRY__PORT=${stack.sandbox_registry_port || ""}`); + lines.push(`STACK__STACK__SANDBOX_REGISTRY__URL=${stack.sandbox_registry_url || ""}`); + } + if (stack.egress_proxy) { + lines.push(`STACK__STACK__EGRESS_PROXY__AUTH_TOKEN=${formatEnvValue(stack.egress_proxy.auth_token)}`); + } + lines.push(""); + + // Derived + lines.push("# Derived"); + lines.push(`STACK__STACK__INSTANCES_DIR=${formatEnvValue(installDir + "/instances")}`); + lines.push(`STACK__STACK__IMAGE=${formatEnvValue("openclaw-" + projectName + ":local")}`); + lines.push(`STACK__CLAWS__IDS=${formatEnvValue(Object.keys(claws).join(","))}`); + lines.push(""); + + // Derived: host alerter schedule + const hostAlerter = (config.host || {}).host_alerter || {}; + const schedule = parseDailyReportTime(hostAlerter.daily_report); + lines.push("# Derived: host alerter schedule"); + lines.push(`STACK__HOST__HOSTALERT__CRON_EXPR=${formatEnvValue(schedule.cronExpr)}`); + lines.push(`STACK__HOST__HOSTALERT__CRON_TZ=${formatEnvValue(schedule.ianaTz)}`); + lines.push(""); + + // Per-claw (merged with defaults) + lines.push("# Per-claw (merged with defaults)"); + for (const [name, claw] of Object.entries(claws)) { + const envKey = name.replace(/-/g, "_").toUpperCase(); + lines.push(`STACK__CLAWS__${envKey}__DOMAIN=${formatEnvValue(claw.domain || "")}`); + lines.push(`STACK__CLAWS__${envKey}__DASHBOARD_PATH=${formatEnvValue(claw.dashboard_path || "")}`); + lines.push(`STACK__CLAWS__${envKey}__GATEWAY_PORT=${claw.gateway_port || ""}`); + lines.push(`STACK__CLAWS__${envKey}__DASHBOARD_PORT=${claw.dashboard_port || ""}`); + lines.push(`STACK__CLAWS__${envKey}__HEALTH_CHECK_CRON=${claw.health_check_cron ?? false}`); + } + lines.push(""); + + return lines.join("\n"); +} + +// ── Auto Token Resolution ──────────────────────────────────────────────────── + +/** + * Resolve an auto-generated token: explicit value > cached from previous build > new random. + * @param {string} value - Explicit value (if set) + * @param {string} cachePath - Dot-separated path into previousDeploy + * @param {object|null} previousDeploy - Previous deploy config + * @param {() => string} generateFn - Token generator function + * @returns {string} Resolved token + */ +export function resolveAutoToken(value, cachePath, previousDeploy, generateFn) { + if (value) return value; + let cached = previousDeploy; + for (const key of cachePath.split(".")) { + cached = cached?.[key]; + } + if (cached) return cached; + return generateFn(); +} diff --git a/build/pre-deploy.mjs b/build/pre-deploy.mjs index 3082952..8f02577 100644 --- a/build/pre-deploy.mjs +++ b/build/pre-deploy.mjs @@ -19,6 +19,19 @@ import * as yaml from "js-yaml"; import * as dotenv from "dotenv"; import Handlebars from "handlebars"; import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser"; +import { + resolveEnvRefs as _resolveEnvRefs, + isPlainObject, + deepMerge, + parseMemoryValue, + formatMemory, + parseJsoncFile as _parseJsoncFile, + validateClaw as _validateClaw, + parseDailyReportTime as _parseDailyReportTime, + formatEnvValue, + generateStackEnv, + resolveAutoToken as _resolveAutoToken, +} from "./pre-deploy-lib.mjs"; // ── Constants ──────────────────────────────────────────────────────────────── @@ -69,15 +82,10 @@ function loadPreviousDeploy() { * Tokens resolved this way persist in stack.json between builds, so they remain stable * across `npm run pre-deploy` runs without requiring the user to set them in .env. */ +const defaultTokenGenerator = () => randomUUID().replace(/-/g, "") + randomUUID().replace(/-/g, ""); + function resolveAutoToken(value, cachePath, previousDeploy) { - if (value) return value; - // Walk dot-separated path into previous deploy config - let cached = previousDeploy; - for (const key of cachePath.split(".")) { - cached = cached?.[key]; - } - if (cached) return cached; - return randomUUID().replace(/-/g, "") + randomUUID().replace(/-/g, ""); + return _resolveAutoToken(value, cachePath, previousDeploy, defaultTokenGenerator); } // ── Protected Vars ─────────────────────────────────────────────────────────── @@ -135,49 +143,10 @@ function readDotEnv() { // ── Step 2: Resolve ${VAR} in stack.yml ────────────────────────────────────── function resolveEnvRefs(text, env) { - // Process line-by-line to skip YAML comments - return text.split("\n").map((line) => { - // Skip comment lines (YAML comments start with optional whitespace + #) - if (line.trimStart().startsWith("#")) return line; - - // Match ${VAR} and ${VAR:-default} - return line.replace(/\$\{([^}]+)\}/g, (_match, expr) => { - const defaultMatch = expr.match(/^([^:]+):-(.*)$/); - if (defaultMatch) { - const key = defaultMatch[1]; - const defaultVal = defaultMatch[2]; - return env[key] !== undefined && env[key] !== "" ? env[key] : defaultVal; - } - const value = env[expr]; - if (value === undefined) { - warn(`Unresolved env var: \${${expr}} (will be empty string)`); - return ""; - } - return value; - }); - }).join("\n"); -} - -// ── Step 3: Deep merge ─────────────────────────────────────────────────────── - -function isPlainObject(val) { - return typeof val === "object" && val !== null && !Array.isArray(val); + return _resolveEnvRefs(text, env, (msg) => warn(msg)); } -/** Deep merge source into target. Source values win at any depth. */ -function deepMerge(target, source) { - const result = { ...target }; - for (const key of Object.keys(source)) { - const srcVal = source[key]; - const tgtVal = result[key]; - if (isPlainObject(srcVal) && isPlainObject(tgtVal)) { - result[key] = deepMerge(tgtVal, srcVal); - } else { - result[key] = srcVal; - } - } - return result; -} +// ── Step 3: Deep merge (imported from pre-deploy-lib.mjs) ──────────────────── // ── Step 4: Resolve resource percentages ───────────────────────────────────── @@ -225,20 +194,7 @@ async function queryVpsCapacity(env) { return { cpus, memory_mb: memMb }; } -function parseMemoryValue(val) { - const str = String(val).trim(); - const match = str.match(/^(\d+(?:\.\d+)?)\s*(G|M|GB|MB|g|m|gb|mb)?$/i); - if (!match) fatal(`Invalid memory value: ${val}`); - const num = parseFloat(match[1]); - const unit = (match[2] || "M").toUpperCase().replace("B", ""); - const mb = unit === "G" ? Math.floor(num * 1024) : Math.floor(num); - return { mb, original: str }; -} - -function formatMemory(mb) { - if (mb >= 1024 && mb % 1024 === 0) return `${mb / 1024}G`; - return `${mb}M`; -} +// parseMemoryValue and formatMemory imported from pre-deploy-lib.mjs async function resolveStackResources(stackResources, env) { const maxCpu = String(stackResources?.max_cpu || "100%"); @@ -271,31 +227,23 @@ async function resolveStackResources(stackResources, env) { return { max_cpu: resolvedCpu, max_mem_mb: resolvedMemMb }; } -// ── Step 5: Parse JSONC (JSON with Comments) ───────────────────────────────── +// ── Step 5: Parse JSONC (imported from pre-deploy-lib.mjs) ─────────────────── function parseJsoncFile(text, filePath) { - const errors = []; - const result = parseJsonc(text, errors, { allowTrailingComma: true }); - if (errors.length > 0) { - const errorMsgs = errors.map(e => ` offset ${e.offset}: ${printParseErrorCode(e.error)}`).join("\n"); - fatal(`JSONC parse errors in ${filePath}:\n${errorMsgs}`); + try { + return _parseJsoncFile(text, filePath); + } catch (e) { + fatal(e.message); } - return result; } -// ── Step 6: Validate required fields ───────────────────────────────────────── +// ── Step 6: Validate required fields (imported from pre-deploy-lib.mjs) ────── function validateClaw(name, claw) { - const required = ["domain", "gateway_port", "dashboard_port"]; - for (const field of required) { - if (claw[field] === undefined || claw[field] === "") { - fatal(`Claw '${name}' is missing required field: ${field}`); - } - } - - const telegram = claw.telegram; - if (!telegram?.bot_token) { - warn(`Claw '${name}' has no telegram.bot_token — Telegram will be disabled`); + try { + _validateClaw(name, claw, (msg) => warn(msg)); + } catch (e) { + fatal(e.message); } } @@ -361,139 +309,11 @@ function computeDerivedValues(claws, stack, host, previousDeploy) { return autoTokens; } -// ── Step 8: Parse human-readable time → cron expression + IANA timezone ────── - -const TZ_ABBREVIATIONS = { - PST: "America/Los_Angeles", PDT: "America/Los_Angeles", - EST: "America/New_York", EDT: "America/New_York", - CST: "America/Chicago", CDT: "America/Chicago", - MST: "America/Denver", MDT: "America/Denver", - UTC: "UTC", GMT: "Europe/London", - CET: "Europe/Berlin", CEST: "Europe/Berlin", -}; +// ── Steps 8-9: parseDailyReportTime, formatEnvValue, generateStackEnv ──────── +// Imported from pre-deploy-lib.mjs function parseDailyReportTime(timeStr) { - const fallback = { cronExpr: "30 9 * * *", ianaTz: "America/Los_Angeles" }; - if (!timeStr) return fallback; - - const match = String(timeStr).match(/^(\d{1,2}):(\d{2})\s*(AM|PM)\s*(\w+)$/i); - if (!match) { - warn(`Could not parse daily_report time "${timeStr}" — using default 9:30 AM PST`); - return fallback; - } - - let hour = parseInt(match[1], 10); - const minute = parseInt(match[2], 10); - const ampm = match[3].toUpperCase(); - const tzAbbr = match[4].toUpperCase(); - - if (ampm === "PM" && hour !== 12) hour += 12; - if (ampm === "AM" && hour === 12) hour = 0; - - const ianaTz = TZ_ABBREVIATIONS[tzAbbr]; - if (!ianaTz) { - warn(`Unknown timezone abbreviation "${match[4]}" — using America/Los_Angeles`); - return { cronExpr: `${minute} ${hour} * * *`, ianaTz: "America/Los_Angeles" }; - } - - return { cronExpr: `${minute} ${hour} * * *`, ianaTz }; -} - -// ── Step 9: Generate stack.env ─────────────────────────────────────────────── -// Bash-sourceable key=value file for shell scripts. -// Convention: ENV__ for .env vars, STACK__ for stack.yml vars. - -function formatEnvValue(val) { - const s = String(val ?? ""); - if (s === "") return ""; - if (/[\s'"\\$`!#&|;()<>{}]/.test(s)) { - return "'" + s.replace(/'/g, "'\\''") + "'"; - } - return s; -} - -function generateStackEnv(env, config, claws) { - const lines = [ - "# Generated by pre-deploy — DO NOT EDIT", - "# To regenerate: npm run pre-deploy", - "", - ]; - - // Source: .env - const envVars = [ - "VPS_IP", "SSH_KEY", "SSH_PORT", "SSH_USER", - "HOSTALERT_TELEGRAM_BOT_TOKEN", "HOSTALERT_TELEGRAM_CHAT_ID", - "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_TUNNEL_TOKEN", - ]; - lines.push("# Source: .env"); - for (const key of envVars) { - if (env[key] !== undefined) { - lines.push(`ENV__${key}=${formatEnvValue(env[key])}`); - } - } - lines.push(""); - - const stack = config.stack || {}; - const host = config.host || {}; - - // Source: stack.yml → host - lines.push("# Source: stack.yml → host"); - if (host.hostname) lines.push(`STACK__HOST__HOSTNAME=${formatEnvValue(host.hostname)}`); - lines.push(""); - - // Source: stack.yml → stack - const installDir = String(stack.install_dir || "/home/openclaw"); - const projectName = String(stack.project_name || "openclaw-stack"); - lines.push("# Source: stack.yml → stack"); - lines.push(`STACK__STACK__INSTALL_DIR=${formatEnvValue(installDir)}`); - lines.push(`STACK__STACK__PROJECT_NAME=${formatEnvValue(projectName)}`); - lines.push(`STACK__STACK__LOGGING__VECTOR=${stack.logging?.vector ?? false}`); - if (stack.openclaw?.version) { - lines.push(`STACK__STACK__OPENCLAW__VERSION=${formatEnvValue(stack.openclaw.version)}`); - } - if (stack.openclaw?.source) { - lines.push(`STACK__STACK__OPENCLAW__SOURCE=${formatEnvValue(stack.openclaw.source)}`); - } - if (stack.sandbox_toolkit) { - lines.push(`STACK__STACK__SANDBOX_TOOLKIT=${formatEnvValue(stack.sandbox_toolkit)}`); - } - if (stack.sandbox_registry) { - lines.push(`STACK__STACK__SANDBOX_REGISTRY__PORT=${stack.sandbox_registry_port || ""}`); - lines.push(`STACK__STACK__SANDBOX_REGISTRY__URL=${stack.sandbox_registry_url || ""}`); - } - if (stack.egress_proxy) { - lines.push(`STACK__STACK__EGRESS_PROXY__AUTH_TOKEN=${formatEnvValue(stack.egress_proxy.auth_token)}`); - } - lines.push(""); - - // Derived - lines.push("# Derived"); - lines.push(`STACK__STACK__INSTANCES_DIR=${formatEnvValue(installDir + "/instances")}`); - lines.push(`STACK__STACK__IMAGE=${formatEnvValue("openclaw-" + projectName + ":local")}`); - lines.push(`STACK__CLAWS__IDS=${formatEnvValue(Object.keys(claws).join(","))}`); - lines.push(""); - - // Derived: host alerter schedule - const hostAlerter = (config.host || {}).host_alerter || {}; - const schedule = parseDailyReportTime(hostAlerter.daily_report); - lines.push("# Derived: host alerter schedule"); - lines.push(`STACK__HOST__HOSTALERT__CRON_EXPR=${formatEnvValue(schedule.cronExpr)}`); - lines.push(`STACK__HOST__HOSTALERT__CRON_TZ=${formatEnvValue(schedule.ianaTz)}`); - lines.push(""); - - // Per-claw (merged with defaults) - lines.push("# Per-claw (merged with defaults)"); - for (const [name, claw] of Object.entries(claws)) { - const envKey = name.replace(/-/g, "_").toUpperCase(); - lines.push(`STACK__CLAWS__${envKey}__DOMAIN=${formatEnvValue(claw.domain || "")}`); - lines.push(`STACK__CLAWS__${envKey}__DASHBOARD_PATH=${formatEnvValue(claw.dashboard_path || "")}`); - lines.push(`STACK__CLAWS__${envKey}__GATEWAY_PORT=${claw.gateway_port || ""}`); - lines.push(`STACK__CLAWS__${envKey}__DASHBOARD_PORT=${claw.dashboard_port || ""}`); - lines.push(`STACK__CLAWS__${envKey}__HEALTH_CHECK_CRON=${claw.health_check_cron ?? false}`); - } - lines.push(""); - - return lines.join("\n"); + return _parseDailyReportTime(timeStr, (msg) => warn(msg)); } // ── Main ───────────────────────────────────────────────────────────────────── diff --git a/docs/TESTING.md b/docs/TESTING.md index efb003a..31e28fe 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -2,6 +2,8 @@ This document provides comprehensive testing instructions for verifying an existing OpenClaw single-VPS deployment. It combines SSH-based verification (delegated to the verification playbook) with browser UI tests via Chrome DevTools MCP. +> **Pre-deploy tests:** Run `npm test` before deploying to catch config resolution, routing, and build-patch regressions locally. See test suite in `test/` and `workers/ai-gateway/test/`. + ## For Claude Code Agents When asked to test the OpenClaw deployment, follow both phases below. Source the config first for connection details and variable values used throughout. diff --git a/package-lock.json b/package-lock.json index 866f607..023fe9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,977 @@ "handlebars": "^4.7.8", "js-yaml": "^4.1.0", "jsonc-parser": "^3.3.1" + }, + "devDependencies": { + "vitest": "^4.0.18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/argparse": { "version": "2.0.1", "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/dotenv": { "version": "17.3.1", "license": "BSD-2-Clause", @@ -26,6 +991,108 @@ "url": "https://dotenvx.com" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/handlebars": { "version": "4.7.8", "license": "MIT", @@ -59,6 +1126,16 @@ "version": "3.3.1", "license": "MIT" }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/minimist": { "version": "1.2.8", "license": "MIT", @@ -66,10 +1143,148 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/neo-async": { "version": "2.6.2", "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.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.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map": { "version": "0.6.1", "license": "BSD-3-Clause", @@ -77,6 +1292,74 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "license": "BSD-2-Clause", @@ -88,6 +1371,176 @@ "node": ">=0.8.0" } }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "license": "MIT" diff --git a/package.json b/package.json index e01b19a..fabfa8e 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,16 @@ "private": true, "scripts": { "pre-deploy": "node build/pre-deploy.mjs", - "pre-deploy:dry": "node build/pre-deploy.mjs --dry-run" + "pre-deploy:dry": "node build/pre-deploy.mjs --dry-run", + "test": "vitest run && npm test --prefix workers/ai-gateway" }, "dependencies": { "dotenv": "^17.3.1", "handlebars": "^4.7.8", "js-yaml": "^4.1.0", "jsonc-parser": "^3.3.1" + }, + "devDependencies": { + "vitest": "^4.0.18" } -} \ No newline at end of file +} diff --git a/test/compose-template.test.mjs b/test/compose-template.test.mjs new file mode 100644 index 0000000..1e43dd1 --- /dev/null +++ b/test/compose-template.test.mjs @@ -0,0 +1,173 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; +import Handlebars from "handlebars"; +import * as yaml from "js-yaml"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, ".."); +const templateSrc = readFileSync(join(ROOT, "docker-compose.yml.hbs"), "utf-8"); +const template = Handlebars.compile(templateSrc, { noEscape: true }); + +function render(overrides = {}) { + const defaults = { + stack: { + project_name: "test-stack", + install_dir: "/home/openclaw", + cloudflare: { tunnel_token: "cf-token" }, + ...overrides.stack, + }, + claws: overrides.claws ?? { + main: { + gateway_port: 18789, + dashboard_port: 6090, + gateway_token: "gw-tok", + domain: "example.com", + domain_path: "", + dashboard_path: "/dash", + allowed_origin: "https://example.com", + allow_updates: false, + anthropic_api_key: "ak", + anthropic_base_url: "https://gw/anthropic", + openai_api_key: "ok", + openai_base_url: "https://gw/openai/v1", + openai_codex_base_url: "https://gw/openai-codex", + telegram: { bot_token: "bot:tok", allow_from: "123" }, + vps_hostname: "vps1", + log_worker_url: "", + log_worker_token: "", + events_url: "", + llemtry_url: "", + enable_events_logging: false, + enable_llemtry_logging: false, + resources: { cpus: "4", memory: "8G" }, + mdns_hostname: "main", + }, + }, + }; + const rendered = template(defaults); + return { rendered, parsed: yaml.load(rendered) }; +} + +describe("docker-compose.yml.hbs", () => { + it("renders valid YAML", () => { + const { parsed } = render(); + expect(parsed).toBeDefined(); + expect(parsed.services).toBeDefined(); + }); + + it("generates correct service name for single claw", () => { + const { parsed } = render(); + expect(parsed.services["test-stack-openclaw-main"]).toBeDefined(); + }); + + it("binds ports to 127.0.0.1", () => { + const { rendered } = render(); + expect(rendered).toContain("127.0.0.1:18789:18789"); + expect(rendered).toContain("127.0.0.1:6090:6090"); + }); + + it("sets environment variables", () => { + const { rendered } = render(); + expect(rendered).toContain("OPENCLAW_GATEWAY_PORT=18789"); + expect(rendered).toContain("NODE_ENV=production"); + }); + + it("always outputs TELEGRAM_BOT_TOKEN", () => { + const { rendered } = render(); + expect(rendered).toContain("TELEGRAM_BOT_TOKEN=bot:tok"); + expect(rendered).toContain("ADMIN_TELEGRAM_ID=123"); + }); + + it("generates multiple claw services", () => { + const claws = { + main: { + gateway_port: 18789, dashboard_port: 6090, gateway_token: "t1", + domain: "a.com", domain_path: "", dashboard_path: "/d1", + allowed_origin: "https://a.com", allow_updates: false, + anthropic_api_key: "ak", anthropic_base_url: "u", + openai_api_key: "ok", openai_base_url: "u", openai_codex_base_url: "u", + telegram: { bot_token: "", allow_from: "" }, + vps_hostname: "", log_worker_url: "", log_worker_token: "", + events_url: "", llemtry_url: "", + enable_events_logging: false, enable_llemtry_logging: false, + resources: { cpus: "4", memory: "8G" }, + }, + secondary: { + gateway_port: 18790, dashboard_port: 6091, gateway_token: "t2", + domain: "b.com", domain_path: "", dashboard_path: "/d2", + allowed_origin: "https://b.com", allow_updates: false, + anthropic_api_key: "ak", anthropic_base_url: "u", + openai_api_key: "ok", openai_base_url: "u", openai_codex_base_url: "u", + telegram: { bot_token: "", allow_from: "" }, + vps_hostname: "", log_worker_url: "", log_worker_token: "", + events_url: "", llemtry_url: "", + enable_events_logging: false, enable_llemtry_logging: false, + resources: { cpus: "2", memory: "4G" }, + }, + }; + const { parsed } = render({ claws }); + expect(parsed.services["test-stack-openclaw-main"]).toBeDefined(); + expect(parsed.services["test-stack-openclaw-secondary"]).toBeDefined(); + }); + + it("includes cloudflared service", () => { + const { parsed } = render(); + expect(parsed.services["cloudflared"]).toBeDefined(); + }); + + it("conditionally includes vector service", () => { + const { parsed: without } = render(); + expect(without.services["vector"]).toBeUndefined(); + + const { parsed: withVector } = render({ + stack: { + vector: true, + logging: { worker_url: "https://log.example.com", worker_token: "lt" }, + }, + }); + expect(withVector.services["vector"]).toBeDefined(); + }); + + it("conditionally includes egress-proxy service", () => { + const { parsed: without } = render(); + expect(without.services["egress-proxy"]).toBeUndefined(); + + const { parsed: withProxy } = render({ + stack: { + egress_proxy: { port: 8080, auth_token: "at", log_level: "info" }, + }, + }); + expect(withProxy.services["egress-proxy"]).toBeDefined(); + }); + + it("conditionally includes sandbox-registry service", () => { + const { parsed: without } = render(); + expect(without.services["sandbox-registry"]).toBeUndefined(); + + const { parsed: withReg } = render({ + stack: { + sandbox_registry_container: true, + sandbox_registry: { port: 5000, token: "sr-tok", log_level: "warn" }, + }, + }); + expect(withReg.services["sandbox-registry"]).toBeDefined(); + }); + + it("sets resource limits from claw config", () => { + const { rendered } = render(); + expect(rendered).toContain('cpus: "4"'); + expect(rendered).toContain('memory: "8G"'); + }); + + it("sets healthcheck with correct port", () => { + const { rendered } = render(); + expect(rendered).toContain("http://localhost:18789/"); + }); + + it("includes openclaw-net network", () => { + const { parsed } = render(); + expect(parsed.networks["openclaw-net"]).toBeDefined(); + }); +}); diff --git a/test/fixtures/Dockerfile.oci-label b/test/fixtures/Dockerfile.oci-label new file mode 100644 index 0000000..9f3268e --- /dev/null +++ b/test/fixtures/Dockerfile.oci-label @@ -0,0 +1,9 @@ +FROM docker.io/library/node:22-bookworm-slim AS base +LABEL org.opencontainers.image.source="https://github.com/example/openclaw" +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN corepack enable && pnpm install --frozen-lockfile +COPY . . +RUN pnpm build +USER node +CMD ["node", "dist/index.js"] diff --git a/test/fixtures/Dockerfile.patched b/test/fixtures/Dockerfile.patched new file mode 100644 index 0000000..0c1805a --- /dev/null +++ b/test/fixtures/Dockerfile.patched @@ -0,0 +1,9 @@ +FROM node:22-bookworm-slim AS base +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN corepack enable && pnpm install --frozen-lockfile +COPY . . +RUN pnpm build +RUN apt-get update && apt-get install -y --no-install-recommends docker.io gosu gettext-base && usermod -aG docker node && rm -rf /var/lib/apt/lists/* +USER node +CMD ["node", "dist/index.js"] diff --git a/test/fixtures/Dockerfile.unpatched b/test/fixtures/Dockerfile.unpatched new file mode 100644 index 0000000..db19cee --- /dev/null +++ b/test/fixtures/Dockerfile.unpatched @@ -0,0 +1,8 @@ +FROM node:22-bookworm-slim AS base +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN corepack enable && pnpm install --frozen-lockfile +COPY . . +RUN pnpm build +USER node +CMD ["node", "dist/index.js"] diff --git a/test/fixtures/dockerignore.no-git b/test/fixtures/dockerignore.no-git new file mode 100644 index 0000000..e6367a1 --- /dev/null +++ b/test/fixtures/dockerignore.no-git @@ -0,0 +1,3 @@ +node_modules +.env +dist diff --git a/test/fixtures/dockerignore.with-data b/test/fixtures/dockerignore.with-data new file mode 100644 index 0000000..7e8f7d0 --- /dev/null +++ b/test/fixtures/dockerignore.with-data @@ -0,0 +1,6 @@ +node_modules +.env +.git +dist +data/ +deploy/ diff --git a/test/fixtures/dockerignore.with-git b/test/fixtures/dockerignore.with-git new file mode 100644 index 0000000..91eaf0d --- /dev/null +++ b/test/fixtures/dockerignore.with-git @@ -0,0 +1,4 @@ +node_modules +.env +.git +dist diff --git a/test/fixtures/send-queue.broken.ts b/test/fixtures/send-queue.broken.ts new file mode 100644 index 0000000..0d87284 --- /dev/null +++ b/test/fixtures/send-queue.broken.ts @@ -0,0 +1,8 @@ +import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue"; + +export class SendQueue { + private queue = new KeyedAsyncQueue(); + async send(key: string, message: string) { + return this.queue.run(key, async () => message); + } +} diff --git a/test/fixtures/send-queue.fixed.ts b/test/fixtures/send-queue.fixed.ts new file mode 100644 index 0000000..7ebed62 --- /dev/null +++ b/test/fixtures/send-queue.fixed.ts @@ -0,0 +1,8 @@ +import { KeyedAsyncQueue } from "openclaw/plugin-sdk"; + +export class SendQueue { + private queue = new KeyedAsyncQueue(); + async send(key: string, message: string) { + return this.queue.run(key, async () => message); + } +} diff --git a/test/pre-deploy/deep-merge.test.mjs b/test/pre-deploy/deep-merge.test.mjs new file mode 100644 index 0000000..512dd9f --- /dev/null +++ b/test/pre-deploy/deep-merge.test.mjs @@ -0,0 +1,65 @@ +import { describe, it, expect } from "vitest"; +import { deepMerge, isPlainObject } from "../../build/pre-deploy-lib.mjs"; + +describe("isPlainObject", () => { + it("returns true for plain objects", () => { + expect(isPlainObject({})).toBe(true); + expect(isPlainObject({ a: 1 })).toBe(true); + }); + + it("returns false for arrays", () => { + expect(isPlainObject([])).toBe(false); + }); + + it("returns false for null", () => { + expect(isPlainObject(null)).toBe(false); + }); + + it("returns false for primitives", () => { + expect(isPlainObject(42)).toBe(false); + expect(isPlainObject("str")).toBe(false); + expect(isPlainObject(undefined)).toBe(false); + }); +}); + +describe("deepMerge", () => { + it("source wins at leaf level", () => { + const result = deepMerge({ a: 1 }, { a: 2 }); + expect(result.a).toBe(2); + }); + + it("preserves target keys not in source", () => { + const result = deepMerge({ a: 1, b: 2 }, { a: 3 }); + expect(result).toEqual({ a: 3, b: 2 }); + }); + + it("adds new keys from source", () => { + const result = deepMerge({ a: 1 }, { b: 2 }); + expect(result).toEqual({ a: 1, b: 2 }); + }); + + it("recursively merges nested objects", () => { + const result = deepMerge( + { nested: { a: 1, b: 2 } }, + { nested: { b: 3, c: 4 } } + ); + expect(result.nested).toEqual({ a: 1, b: 3, c: 4 }); + }); + + it("replaces arrays (no array merging)", () => { + const result = deepMerge({ arr: [1, 2] }, { arr: [3] }); + expect(result.arr).toEqual([3]); + }); + + it("null in source overwrites target", () => { + const result = deepMerge({ a: { b: 1 } }, { a: null }); + expect(result.a).toBeNull(); + }); + + it("does not mutate target", () => { + const target = { a: 1 }; + const result = deepMerge(target, { a: 2 }); + expect(target.a).toBe(1); + expect(result.a).toBe(2); + }); +}); diff --git a/test/pre-deploy/format-env-value.test.mjs b/test/pre-deploy/format-env-value.test.mjs new file mode 100644 index 0000000..50172dd --- /dev/null +++ b/test/pre-deploy/format-env-value.test.mjs @@ -0,0 +1,34 @@ +import { describe, it, expect } from "vitest"; +import { formatEnvValue } from "../../build/pre-deploy-lib.mjs"; + +describe("formatEnvValue", () => { + it("passes through plain strings", () => { + expect(formatEnvValue("hello")).toBe("hello"); + }); + + it("single-quotes strings with spaces", () => { + expect(formatEnvValue("hello world")).toBe("'hello world'"); + }); + + it("escapes single quotes inside values", () => { + expect(formatEnvValue("it's")).toBe("'it'\\''s'"); + }); + + it("quotes strings with dollar signs", () => { + expect(formatEnvValue("$HOME")).toBe("'$HOME'"); + }); + + it("returns empty string for null/undefined", () => { + expect(formatEnvValue(null)).toBe(""); + expect(formatEnvValue(undefined)).toBe(""); + }); + + it("converts numbers to strings", () => { + expect(formatEnvValue(8080)).toBe("8080"); + }); + + it("converts booleans to strings", () => { + expect(formatEnvValue(false)).toBe("false"); + expect(formatEnvValue(true)).toBe("true"); + }); +}); diff --git a/test/pre-deploy/generate-stack-env.test.mjs b/test/pre-deploy/generate-stack-env.test.mjs new file mode 100644 index 0000000..0faec9d --- /dev/null +++ b/test/pre-deploy/generate-stack-env.test.mjs @@ -0,0 +1,84 @@ +import { describe, it, expect } from "vitest"; +import { generateStackEnv } from "../../build/pre-deploy-lib.mjs"; + +describe("generateStackEnv", () => { + const env = { + VPS_IP: "10.0.0.1", + SSH_KEY: "/home/user/.ssh/id_ed25519", + SSH_PORT: "222", + SSH_USER: "adminclaw", + }; + + const config = { + stack: { + install_dir: "/home/openclaw", + project_name: "openclaw-stack", + logging: { vector: true }, + openclaw: { version: "stable" }, + }, + host: { + hostname: "vps1", + host_alerter: { daily_report: "9:30 AM PST" }, + }, + }; + + const claws = { + main: { + domain: "example.com", + dashboard_path: "/dash", + gateway_port: 18789, + dashboard_port: 6090, + }, + }; + + it("includes ENV__ vars from .env", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("ENV__VPS_IP=10.0.0.1"); + expect(result).toContain("ENV__SSH_PORT=222"); + }); + + it("includes stack config", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__STACK__INSTALL_DIR=/home/openclaw"); + expect(result).toContain("STACK__STACK__PROJECT_NAME=openclaw-stack"); + }); + + it("includes derived values", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__STACK__IMAGE=openclaw-openclaw-stack:local"); + expect(result).toContain("STACK__STACK__INSTANCES_DIR=/home/openclaw/instances"); + expect(result).toContain("STACK__CLAWS__IDS=main"); + }); + + it("includes per-claw values", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__CLAWS__MAIN__DOMAIN=example.com"); + expect(result).toContain("STACK__CLAWS__MAIN__GATEWAY_PORT=18789"); + }); + + it("includes host alerter schedule", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__HOST__HOSTALERT__CRON_EXPR="); + expect(result).toContain("STACK__HOST__HOSTALERT__CRON_TZ="); + }); + + it("handles multiple claws", () => { + const multiClaws = { + main: { ...claws.main }, + secondary: { domain: "s.com", gateway_port: 18790, dashboard_port: 6091 }, + }; + const result = generateStackEnv(env, config, multiClaws); + expect(result).toContain("STACK__CLAWS__IDS=main,secondary"); + expect(result).toContain("STACK__CLAWS__SECONDARY__DOMAIN=s.com"); + }); + + it("includes openclaw version when set", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__STACK__OPENCLAW__VERSION=stable"); + }); + + it("includes logging vector flag", () => { + const result = generateStackEnv(env, config, claws); + expect(result).toContain("STACK__STACK__LOGGING__VECTOR=true"); + }); +}); diff --git a/test/pre-deploy/parse-daily-report-time.test.mjs b/test/pre-deploy/parse-daily-report-time.test.mjs new file mode 100644 index 0000000..6f42d2f --- /dev/null +++ b/test/pre-deploy/parse-daily-report-time.test.mjs @@ -0,0 +1,52 @@ +import { describe, it, expect, vi } from "vitest"; +import { parseDailyReportTime, TZ_ABBREVIATIONS } from "../../build/pre-deploy-lib.mjs"; + +describe("parseDailyReportTime", () => { + it("parses '9:30 AM PST'", () => { + const result = parseDailyReportTime("9:30 AM PST"); + expect(result.cronExpr).toBe("30 9 * * *"); + expect(result.ianaTz).toBe("America/Los_Angeles"); + }); + + it("converts PM correctly", () => { + const result = parseDailyReportTime("2:00 PM EST"); + expect(result.cronExpr).toBe("0 14 * * *"); + expect(result.ianaTz).toBe("America/New_York"); + }); + + it("handles 12 AM (midnight)", () => { + const result = parseDailyReportTime("12:00 AM UTC"); + expect(result.cronExpr).toBe("0 0 * * *"); + }); + + it("handles 12 PM (noon)", () => { + const result = parseDailyReportTime("12:00 PM UTC"); + expect(result.cronExpr).toBe("0 12 * * *"); + }); + + it("returns fallback for null input", () => { + const result = parseDailyReportTime(null); + expect(result.cronExpr).toBe("30 9 * * *"); + expect(result.ianaTz).toBe("America/Los_Angeles"); + }); + + it("returns fallback for unparseable input", () => { + const onWarn = vi.fn(); + const result = parseDailyReportTime("garbage", onWarn); + expect(result.cronExpr).toBe("30 9 * * *"); + expect(onWarn).toHaveBeenCalled(); + }); + + it("warns on unknown timezone", () => { + const onWarn = vi.fn(); + const result = parseDailyReportTime("9:30 AM XYZ", onWarn); + expect(onWarn).toHaveBeenCalledWith(expect.stringContaining("XYZ")); + expect(result.ianaTz).toBe("America/Los_Angeles"); + }); + + it("exports TZ_ABBREVIATIONS", () => { + expect(TZ_ABBREVIATIONS.PST).toBe("America/Los_Angeles"); + expect(TZ_ABBREVIATIONS.EST).toBe("America/New_York"); + expect(TZ_ABBREVIATIONS.UTC).toBe("UTC"); + }); +}); diff --git a/test/pre-deploy/parse-jsonc-file.test.mjs b/test/pre-deploy/parse-jsonc-file.test.mjs new file mode 100644 index 0000000..4ff8b94 --- /dev/null +++ b/test/pre-deploy/parse-jsonc-file.test.mjs @@ -0,0 +1,32 @@ +import { describe, it, expect } from "vitest"; +import { parseJsoncFile } from "../../build/pre-deploy-lib.mjs"; + +describe("parseJsoncFile", () => { + it("parses valid JSON", () => { + const result = parseJsoncFile('{"key": "value"}', "test.json"); + expect(result).toEqual({ key: "value" }); + }); + + it("parses JSON with comments", () => { + const input = `{ + // This is a comment + "key": "value" + }`; + const result = parseJsoncFile(input, "test.jsonc"); + expect(result).toEqual({ key: "value" }); + }); + + it("parses JSON with trailing commas", () => { + const input = '{"a": 1, "b": 2,}'; + const result = parseJsoncFile(input, "test.jsonc"); + expect(result).toEqual({ a: 1, b: 2 }); + }); + + it("throws on invalid JSON", () => { + expect(() => parseJsoncFile("{invalid", "bad.json")).toThrow("JSONC parse errors"); + }); + + it("includes file path in error message", () => { + expect(() => parseJsoncFile("{bad", "my-file.jsonc")).toThrow("my-file.jsonc"); + }); +}); diff --git a/test/pre-deploy/parse-memory.test.mjs b/test/pre-deploy/parse-memory.test.mjs new file mode 100644 index 0000000..dfdb75b --- /dev/null +++ b/test/pre-deploy/parse-memory.test.mjs @@ -0,0 +1,52 @@ +import { describe, it, expect } from "vitest"; +import { parseMemoryValue, formatMemory } from "../../build/pre-deploy-lib.mjs"; + +describe("parseMemoryValue", () => { + it("parses GB values", () => { + expect(parseMemoryValue("12G").mb).toBe(12288); + }); + + it("parses MB values", () => { + expect(parseMemoryValue("1024M").mb).toBe(1024); + }); + + it("defaults to MB when no unit", () => { + expect(parseMemoryValue("512").mb).toBe(512); + }); + + it("handles fractional GB", () => { + expect(parseMemoryValue("1.5G").mb).toBe(1536); + }); + + it("is case insensitive", () => { + expect(parseMemoryValue("4g").mb).toBe(4096); + expect(parseMemoryValue("256m").mb).toBe(256); + }); + + it("handles GB/MB suffix variants", () => { + expect(parseMemoryValue("2GB").mb).toBe(2048); + expect(parseMemoryValue("512MB").mb).toBe(512); + }); + + it("throws on invalid input", () => { + expect(() => parseMemoryValue("abc")).toThrow("Invalid memory value"); + }); + + it("preserves original string", () => { + expect(parseMemoryValue("12G").original).toBe("12G"); + }); +}); + +describe("formatMemory", () => { + it("formats exact GB values", () => { + expect(formatMemory(12288)).toBe("12G"); + }); + + it("formats non-exact values as MB", () => { + expect(formatMemory(1025)).toBe("1025M"); + }); + + it("formats 1G", () => { + expect(formatMemory(1024)).toBe("1G"); + }); +}); diff --git a/test/pre-deploy/resolve-auto-token.test.mjs b/test/pre-deploy/resolve-auto-token.test.mjs new file mode 100644 index 0000000..da116fb --- /dev/null +++ b/test/pre-deploy/resolve-auto-token.test.mjs @@ -0,0 +1,37 @@ +import { describe, it, expect, vi } from "vitest"; +import { resolveAutoToken } from "../../build/pre-deploy-lib.mjs"; + +describe("resolveAutoToken", () => { + const mockGenerate = vi.fn(() => "generated-token"); + + it("returns explicit value when provided", () => { + const result = resolveAutoToken("explicit", "a.b", null, mockGenerate); + expect(result).toBe("explicit"); + expect(mockGenerate).not.toHaveBeenCalled(); + }); + + it("returns cached value from previous deploy", () => { + const prev = { a: { b: "cached-token" } }; + const result = resolveAutoToken("", "a.b", prev, mockGenerate); + expect(result).toBe("cached-token"); + }); + + it("generates new token when no explicit or cached value", () => { + mockGenerate.mockReturnValue("new-token"); + const result = resolveAutoToken("", "a.b", null, mockGenerate); + expect(result).toBe("new-token"); + }); + + it("handles undefined intermediate keys in cache path", () => { + const prev = { a: {} }; + mockGenerate.mockReturnValue("fallback"); + const result = resolveAutoToken("", "a.b.c", prev, mockGenerate); + expect(result).toBe("fallback"); + }); + + it("handles null previous deploy", () => { + mockGenerate.mockReturnValue("fresh"); + const result = resolveAutoToken(null, "x.y", null, mockGenerate); + expect(result).toBe("fresh"); + }); +}); diff --git a/test/pre-deploy/resolve-env-refs.test.mjs b/test/pre-deploy/resolve-env-refs.test.mjs new file mode 100644 index 0000000..2b94c52 --- /dev/null +++ b/test/pre-deploy/resolve-env-refs.test.mjs @@ -0,0 +1,48 @@ +import { describe, it, expect, vi } from "vitest"; +import { resolveEnvRefs } from "../../build/pre-deploy-lib.mjs"; + +describe("resolveEnvRefs", () => { + it("replaces ${VAR} with env value", () => { + const result = resolveEnvRefs("host: ${HOST}", { HOST: "example.com" }); + expect(result).toBe("host: example.com"); + }); + + it("replaces ${VAR:-default} with env value when set", () => { + const result = resolveEnvRefs("port: ${PORT:-8080}", { PORT: "3000" }); + expect(result).toBe("port: 3000"); + }); + + it("uses default when env var is missing", () => { + const result = resolveEnvRefs("port: ${PORT:-8080}", {}); + expect(result).toBe("port: 8080"); + }); + + it("uses default when env var is empty string", () => { + const result = resolveEnvRefs("port: ${PORT:-8080}", { PORT: "" }); + expect(result).toBe("port: 8080"); + }); + + it("skips comment lines", () => { + const input = "# comment: ${SECRET}\nreal: ${HOST}"; + const result = resolveEnvRefs(input, { SECRET: "s3cr3t", HOST: "ok" }); + expect(result).toBe("# comment: ${SECRET}\nreal: ok"); + }); + + it("calls onWarn for unresolved vars", () => { + const onWarn = vi.fn(); + resolveEnvRefs("val: ${MISSING}", {}, onWarn); + expect(onWarn).toHaveBeenCalledWith( + expect.stringContaining("MISSING") + ); + }); + + it("handles multiple refs per line", () => { + const result = resolveEnvRefs("${A}:${B}", { A: "hello", B: "world" }); + expect(result).toBe("hello:world"); + }); + + it("returns empty string for unresolved vars without default", () => { + const result = resolveEnvRefs("val: ${MISSING}", {}, () => {}); + expect(result).toBe("val: "); + }); +}); diff --git a/test/pre-deploy/validate-claw.test.mjs b/test/pre-deploy/validate-claw.test.mjs new file mode 100644 index 0000000..522ccc8 --- /dev/null +++ b/test/pre-deploy/validate-claw.test.mjs @@ -0,0 +1,64 @@ +import { describe, it, expect, vi } from "vitest"; +import { validateClaw } from "../../build/pre-deploy-lib.mjs"; + +describe("validateClaw", () => { + const validClaw = { + domain: "example.com", + gateway_port: 18789, + dashboard_port: 6090, + telegram: { bot_token: "123:abc" }, + }; + + it("accepts a valid claw", () => { + expect(() => validateClaw("main", validClaw, () => {})).not.toThrow(); + }); + + it("throws on missing domain", () => { + const claw = { ...validClaw, domain: undefined }; + expect(() => validateClaw("main", claw, () => {})).toThrow("missing required field: domain"); + }); + + it("throws on missing gateway_port", () => { + const claw = { ...validClaw, gateway_port: undefined }; + expect(() => validateClaw("main", claw, () => {})).toThrow("missing required field: gateway_port"); + }); + + it("throws on missing dashboard_port", () => { + const claw = { ...validClaw, dashboard_port: undefined }; + expect(() => validateClaw("main", claw, () => {})).toThrow("missing required field: dashboard_port"); + }); + + it("throws on empty string domain", () => { + const claw = { ...validClaw, domain: "" }; + expect(() => validateClaw("main", claw, () => {})).toThrow("missing required field: domain"); + }); + + it("warns when telegram.bot_token is missing", () => { + const onWarn = vi.fn(); + const claw = { ...validClaw, telegram: {} }; + validateClaw("main", claw, onWarn); + expect(onWarn).toHaveBeenCalledWith( + expect.stringContaining("telegram.bot_token") + ); + }); + + it("warns when telegram section is missing entirely", () => { + const onWarn = vi.fn(); + const claw = { domain: "ex.com", gateway_port: 1, dashboard_port: 2 }; + validateClaw("main", claw, onWarn); + expect(onWarn).toHaveBeenCalledWith( + expect.stringContaining("telegram.bot_token") + ); + }); + + it("does not warn when telegram.bot_token is present", () => { + const onWarn = vi.fn(); + validateClaw("main", validClaw, onWarn); + expect(onWarn).not.toHaveBeenCalled(); + }); + + it("includes claw name in error messages", () => { + const claw = { ...validClaw, domain: undefined }; + expect(() => validateClaw("test-claw", claw, () => {})).toThrow("test-claw"); + }); +}); diff --git a/test/resolve-config-vars.test.mjs b/test/resolve-config-vars.test.mjs new file mode 100644 index 0000000..cc1fa3a --- /dev/null +++ b/test/resolve-config-vars.test.mjs @@ -0,0 +1,96 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { execSync } from "child_process"; +import { writeFileSync, mkdirSync, rmSync, existsSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, ".."); +const SCRIPT = join(ROOT, "deploy", "openclaw-stack", "resolve-config-vars.mjs"); +const TMP = join(__dirname, ".tmp-resolve-test"); + +function setup(configContent, composeContent) { + rmSync(TMP, { recursive: true, force: true }); + mkdirSync(join(TMP, ".deploy"), { recursive: true }); + const configPath = join(TMP, "openclaw.jsonc"); + writeFileSync(configPath, configContent); + writeFileSync(join(TMP, ".deploy", "docker-compose.yml"), composeContent); + return configPath; +} + +function run(configPath, clawName) { + return execSync(`node "${SCRIPT}" "${configPath}" "${clawName}"`, { + encoding: "utf-8", + }); +} + +function cleanup() { + if (existsSync(TMP)) rmSync(TMP, { recursive: true, force: true }); +} + +const composeYml = ` +services: + test-openclaw-main: + environment: + - TOKEN=abc123 + - PORT=8080 + - EMPTY_VAR= + - DOMAIN=example.com +`; + +describe("resolve-config-vars.mjs", () => { + afterEach(cleanup); + + it("resolves ${VAR} with env value", () => { + const configPath = setup('{"token": "${TOKEN}"}', composeYml); + const result = run(configPath, "main"); + expect(result).toContain('"token": "abc123"'); + }); + + it("resolves ${VAR:-default} with env value when set", () => { + const configPath = setup('{"port": "${PORT:-9090}"}', composeYml); + const result = run(configPath, "main"); + expect(result).toContain('"port": "8080"'); + }); + + it("uses default when var is empty", () => { + const configPath = setup('{"val": "${EMPTY_VAR:-fallback}"}', composeYml); + const result = run(configPath, "main"); + expect(result).toContain('"val": "fallback"'); + }); + + it("uses default when var is missing", () => { + const configPath = setup('{"val": "${MISSING:-default_val}"}', composeYml); + const result = run(configPath, "main"); + expect(result).toContain('"val": "default_val"'); + }); + + it("resolves multiple vars in same file", () => { + const configPath = setup( + '{"a": "${TOKEN}", "b": "${DOMAIN}"}', + composeYml + ); + const result = run(configPath, "main"); + expect(result).toContain('"a": "abc123"'); + expect(result).toContain('"b": "example.com"'); + }); + + it("preserves JSONC comments in output", () => { + const configPath = setup( + '// comment\n{"token": "${TOKEN}"}', + composeYml + ); + const result = run(configPath, "main"); + expect(result).toContain("// comment"); + expect(result).toContain('"token": "abc123"'); + }); + + it("passes through unchanged when compose file is missing", () => { + rmSync(TMP, { recursive: true, force: true }); + mkdirSync(TMP, { recursive: true }); + const configPath = join(TMP, "openclaw.jsonc"); + writeFileSync(configPath, '{"token": "${TOKEN}"}'); + const result = run(configPath, "main"); + expect(result).toContain("${TOKEN}"); + }); +}); diff --git a/test/shell-patterns.test.mjs b/test/shell-patterns.test.mjs new file mode 100644 index 0000000..cbe7f9f --- /dev/null +++ b/test/shell-patterns.test.mjs @@ -0,0 +1,78 @@ +import { describe, it, expect } from "vitest"; +import { execSync } from "child_process"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const FIXTURES = join(__dirname, "fixtures"); + +function grepExits(pattern, file) { + try { + execSync(`grep -q ${pattern} "${file}"`, { stdio: "pipe" }); + return true; + } catch { + return false; + } +} + +describe("build-openclaw.sh grep patterns", () => { + // Main uses: grep -q "docker.io" Dockerfile + // This is the CURRENT pattern on main — it has a known false-positive + // where FROM docker.io/library/node matches even without the install line. + describe('grep -q "docker.io" (main branch pattern)', () => { + it("matches patched Dockerfile (has install line)", () => { + expect(grepExits('"docker.io"', join(FIXTURES, "Dockerfile.patched"))).toBe(true); + }); + + it("does NOT match unpatched Dockerfile", () => { + expect(grepExits('"docker.io"', join(FIXTURES, "Dockerfile.unpatched"))).toBe(false); + }); + + it("FALSE POSITIVE: matches OCI label Dockerfile (known bug)", () => { + // This documents the known bug on main: FROM docker.io/library/node + // triggers the grep, so the patch is skipped even though docker.io + // package isn't installed. Fixed on the deploy fix branch. + expect(grepExits('"docker.io"', join(FIXTURES, "Dockerfile.oci-label"))).toBe(true); + }); + }); + + describe("grep -q '^data/' .dockerignore", () => { + it("matches when data/ exclusion exists", () => { + expect(grepExits("'^data/'", join(FIXTURES, "dockerignore.with-data"))).toBe(true); + }); + + it("does not match when data/ is absent", () => { + expect(grepExits("'^data/'", join(FIXTURES, "dockerignore.with-git"))).toBe(false); + }); + }); + + describe("grep -q '^\\.git$' .dockerignore", () => { + it("matches exact .git line", () => { + expect(grepExits("'^\\.git$'", join(FIXTURES, "dockerignore.with-git"))).toBe(true); + }); + + it("does not match when .git is absent", () => { + expect(grepExits("'^\\.git$'", join(FIXTURES, "dockerignore.no-git"))).toBe(false); + }); + }); + + describe("grep -q 'rm.*tmp/jiti' Dockerfile (jiti cache patch)", () => { + it("does not match unpatched Dockerfile", () => { + expect(grepExits("'rm.*tmp/jiti'", join(FIXTURES, "Dockerfile.unpatched"))).toBe(false); + }); + }); + + describe("broken import pattern", () => { + it("matches broken import path", () => { + expect( + grepExits('"openclaw/plugin-sdk/keyed-async-queue"', join(FIXTURES, "send-queue.broken.ts")) + ).toBe(true); + }); + + it("does not match fixed import path", () => { + expect( + grepExits('"openclaw/plugin-sdk/keyed-async-queue"', join(FIXTURES, "send-queue.fixed.ts")) + ).toBe(false); + }); + }); +}); diff --git a/vitest.config.mjs b/vitest.config.mjs new file mode 100644 index 0000000..1d865bf --- /dev/null +++ b/vitest.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.mjs"], + }, +}); diff --git a/workers/ai-gateway/package-lock.json b/workers/ai-gateway/package-lock.json index 2120f5d..3381580 100644 --- a/workers/ai-gateway/package-lock.json +++ b/workers/ai-gateway/package-lock.json @@ -8,9 +8,2279 @@ "name": "ai-gateway-proxy", "version": "1.0.0", "devDependencies": { - "typescript": "^5.7.3" + "@cloudflare/vitest-pool-workers": "^0.12.20", + "@cloudflare/workers-types": "^4.20260307.1", + "typescript": "^5.7.3", + "vitest": "^3.2.4" } }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.15.0.tgz", + "integrity": "sha512-EGYmJaGZKWl+X8tXxcnx4v2bOZSjQeNI5dWFeXivgX9+YCT69AkzHHwlNbVpqtEUTbew8eQurpyOpeN8fg00nw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/vitest-pool-workers": { + "version": "0.12.20", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.12.20.tgz", + "integrity": "sha512-KzMZTwGTHNfkn8uT+oaVgNnCr6B9j4xeBIDN4XIcXc4MJYVbMyiXrl12aWh7E8GZCg7xm92/NJmjw7aE/2pQDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cjs-module-lexer": "^1.2.3", + "esbuild": "0.27.3", + "miniflare": "4.20260301.1", + "wrangler": "4.71.0" + }, + "peerDependencies": { + "@vitest/runner": "2.0.x - 3.2.x", + "@vitest/snapshot": "2.0.x - 3.2.x", + "vitest": "2.0.x - 3.2.x" + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260301.1.tgz", + "integrity": "sha512-+kJvwociLrvy1JV9BAvoSVsMEIYD982CpFmo/yMEvBwxDIjltYsLTE8DLi0mCkGsQ8Ygidv2fD9wavzXeiY7OQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260301.1.tgz", + "integrity": "sha512-PPIetY3e67YBr9O4UhILK8nbm5TqUDl14qx4rwFNrRSBOvlzuczzbd4BqgpAtbGVFxKp1PWpjAnBvGU/OI/tLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260301.1.tgz", + "integrity": "sha512-Gu5vaVTZuYl3cHa+u5CDzSVDBvSkfNyuAHi6Mdfut7TTUdcb3V5CIcR/mXRSyMXzEy9YxEWIfdKMxOMBjupvYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260301.1.tgz", + "integrity": "sha512-igL1pkyCXW6GiGpjdOAvqMi87UW0LMc/+yIQe/CSzuZJm5GzXoAMrwVTkCFnikk6JVGELrM5x0tGYlxa0sk5Iw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260301.1.tgz", + "integrity": "sha512-Q0wMJ4kcujXILwQKQFc1jaYamVsNvjuECzvRrTI8OxGFMx2yq9aOsswViE4X1gaS2YQQ5u0JGwuGi5WdT1Lt7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260307.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260307.1.tgz", + "integrity": "sha512-0PvWLVVD6Q64V/XhollYtc8H35Vxm2rZi8bkZbEr3lK+mNgd2FBBVhlZ6A3saAUq3giRF4US/UfU/3a8i1PEcg==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz", + "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/miniflare": { + "version": "4.20260301.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260301.1.tgz", + "integrity": "sha512-fqkHx0QMKswRH9uqQQQOU/RoaS3Wjckxy3CUX3YGJr0ZIMu7ObvI+NovdYi6RIsSPthNtq+3TPmRNxjeRiasog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.18.2", + "workerd": "1.20260301.1", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.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.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -24,6 +2294,317 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerd": { + "version": "1.20260301.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260301.1.tgz", + "integrity": "sha512-oterQ1IFd3h7PjCfT4znSFOkJCvNQ6YMOyZ40YsnO3nrSpgB4TbJVYWFOnyJAw71/RQuupfVqZZWKvsy8GO3fw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260301.1", + "@cloudflare/workerd-darwin-arm64": "1.20260301.1", + "@cloudflare/workerd-linux-64": "1.20260301.1", + "@cloudflare/workerd-linux-arm64": "1.20260301.1", + "@cloudflare/workerd-windows-64": "1.20260301.1" + } + }, + "node_modules/wrangler": { + "version": "4.71.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.71.0.tgz", + "integrity": "sha512-j6pSGAncOLNQDRzqtp0EqzYj52CldDP7uz/C9cxVrIgqa5p+cc0b4pIwnapZZAGv9E1Loa3tmPD0aXonH7KTkw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.15.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.3", + "miniflare": "4.20260301.1", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260226.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } } } } diff --git a/workers/ai-gateway/package.json b/workers/ai-gateway/package.json index b6971b8..8594b09 100644 --- a/workers/ai-gateway/package.json +++ b/workers/ai-gateway/package.json @@ -6,9 +6,13 @@ "dev": "wrangler dev", "deploy": "wrangler deploy", "types": "wrangler types --env-file .dev.vars.example", - "typecheck": "npm run types && tsc --noEmit" + "typecheck": "npm run types && tsc --noEmit", + "test": "vitest run" }, "devDependencies": { - "typescript": "^5.7.3" + "@cloudflare/vitest-pool-workers": "^0.12.20", + "@cloudflare/workers-types": "^4.20260307.1", + "typescript": "^5.7.3", + "vitest": "^3.2.4" } -} \ No newline at end of file +} diff --git a/workers/ai-gateway/src/admin.ts b/workers/ai-gateway/src/admin.ts index 53eab25..292bf0b 100644 --- a/workers/ai-gateway/src/admin.ts +++ b/workers/ai-gateway/src/admin.ts @@ -352,13 +352,13 @@ export async function handleCodexTokenGeneration( // --- Mask / Merge helpers --- /** Mask a string to show first 8 + last 4 chars: "sk-ant-api...4f2a" */ -function maskString(s: string): string { +export function maskString(s: string): string { if (s.length <= 16) return '***' return s.slice(0, 8) + '...' + s.slice(-4) } /** Return a masked copy of credentials (safe for client display). */ -function maskCredentials(creds: UserCredentials): Record { +export function maskCredentials(creds: UserCredentials): Record { const result: Record = {} if (creds.anthropic) { @@ -389,7 +389,7 @@ function maskCredentials(creds: UserCredentials): Record { * - Field present with null → delete * - Field absent → keep existing */ -function mergeCredentials( +export function mergeCredentials( existing: UserCredentials, update: Record ): UserCredentials { diff --git a/workers/ai-gateway/test/admin.test.ts b/workers/ai-gateway/test/admin.test.ts new file mode 100644 index 0000000..6a1f5cf --- /dev/null +++ b/workers/ai-gateway/test/admin.test.ts @@ -0,0 +1,151 @@ +import { describe, it, expect } from "vitest"; +import { maskString, maskCredentials, mergeCredentials } from "../src/admin"; +import type { UserCredentials } from "../src/types"; + +describe("maskString", () => { + it("returns *** for short strings", () => { + expect(maskString("short")).toBe("***"); + expect(maskString("exactly16chars!!")).toBe("***"); + }); + + it("shows first 8 + last 4 for long strings", () => { + const long = "sk-ant-api03-abcdefghijklmnop"; + const result = maskString(long); + expect(result).toBe("sk-ant-a...mnop"); + }); + + it("returns *** for empty string", () => { + expect(maskString("")).toBe("***"); + }); +}); + +describe("maskCredentials", () => { + it("masks anthropic apiKey", () => { + const creds: UserCredentials = { + anthropic: { apiKey: "sk-ant-api03-very-long-key-here-1234" }, + }; + const result = maskCredentials(creds); + expect((result.anthropic as Record).apiKey).toContain("..."); + }); + + it("masks openai apiKey", () => { + const creds: UserCredentials = { + openai: { apiKey: "sk-proj-very-long-openai-key-5678" }, + }; + const result = maskCredentials(creds); + expect((result.openai as Record).apiKey).toContain("..."); + }); + + it("shows oauth as configured status", () => { + const creds: UserCredentials = { + openai: { + oauth: { + accessToken: "secret", + refreshToken: "also-secret", + expiresAt: "2026-12-31", + }, + }, + }; + const result = maskCredentials(creds); + const openai = result.openai as Record; + const oauth = openai.oauth as Record; + expect(oauth.status).toBe("configured"); + expect(oauth.expiresAt).toBe("2026-12-31"); + expect(oauth).not.toHaveProperty("accessToken"); + }); + + it("returns empty object for empty creds", () => { + const result = maskCredentials({}); + expect(result).toEqual({}); + }); + + it("handles creds with only anthropic", () => { + const creds: UserCredentials = { + anthropic: { oauthToken: "very-long-anthropic-oauth-token-here" }, + }; + const result = maskCredentials(creds); + expect(result).toHaveProperty("anthropic"); + expect(result).not.toHaveProperty("openai"); + }); +}); + +describe("mergeCredentials", () => { + it("sets a new field", () => { + const existing: UserCredentials = {}; + const update = { anthropic: { apiKey: "new-key" } }; + const result = mergeCredentials(existing, update); + expect(result.anthropic?.apiKey).toBe("new-key"); + }); + + it("updates an existing field", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "old-key" }, + }; + const update = { anthropic: { apiKey: "new-key" } }; + const result = mergeCredentials(existing, update); + expect(result.anthropic?.apiKey).toBe("new-key"); + }); + + it("deletes a field when set to null", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "key", oauthToken: "token" }, + }; + const update = { anthropic: { apiKey: null } }; + const result = mergeCredentials(existing, update); + expect(result.anthropic?.apiKey).toBeUndefined(); + expect(result.anthropic?.oauthToken).toBe("token"); + }); + + it("preserves fields not in update", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "keep" }, + openai: { apiKey: "also-keep" }, + }; + const update = { anthropic: { apiKey: "changed" } }; + const result = mergeCredentials(existing, update); + expect(result.anthropic?.apiKey).toBe("changed"); + expect(result.openai?.apiKey).toBe("also-keep"); + }); + + it("removes entire provider when set to null", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "key" }, + }; + const update = { anthropic: null }; + const result = mergeCredentials(existing, update); + expect(result.anthropic).toBeUndefined(); + }); + + it("removes empty provider section after field deletion", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "only-field" }, + }; + const update = { anthropic: { apiKey: null } }; + const result = mergeCredentials(existing, update); + expect(result.anthropic).toBeUndefined(); + }); + + it("does not mutate existing credentials", () => { + const existing: UserCredentials = { + anthropic: { apiKey: "original" }, + }; + const update = { anthropic: { apiKey: "changed" } }; + mergeCredentials(existing, update); + expect(existing.anthropic?.apiKey).toBe("original"); + }); + + it("handles openai oauth updates", () => { + const existing: UserCredentials = {}; + const update = { + openai: { + oauth: { + accessToken: "at", + refreshToken: "rt", + expiresAt: "2026-12-31", + }, + }, + }; + const result = mergeCredentials(existing, update); + expect(result.openai?.oauth?.accessToken).toBe("at"); + }); +}); diff --git a/workers/ai-gateway/test/auth.test.ts b/workers/ai-gateway/test/auth.test.ts new file mode 100644 index 0000000..9bd0199 --- /dev/null +++ b/workers/ai-gateway/test/auth.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect } from "vitest"; +import { extractToken, authenticateRequest } from "../src/auth"; +import { env } from "cloudflare:test"; + +describe("extractToken", () => { + it("extracts Bearer token from Authorization header", () => { + const req = new Request("https://example.com", { + headers: { Authorization: "Bearer my-token" }, + }); + expect(extractToken(req)).toBe("my-token"); + }); + + it("returns null for non-Bearer Authorization", () => { + const req = new Request("https://example.com", { + headers: { Authorization: "Basic abc" }, + }); + expect(extractToken(req)).toBeNull(); + }); + + it("falls back to x-api-key header", () => { + const req = new Request("https://example.com", { + headers: { "x-api-key": "api-key-123" }, + }); + expect(extractToken(req)).toBe("api-key-123"); + }); + + it("returns null when no auth headers", () => { + const req = new Request("https://example.com"); + expect(extractToken(req)).toBeNull(); + }); + + it("prefers Authorization over x-api-key", () => { + const req = new Request("https://example.com", { + headers: { + Authorization: "Bearer bearer-tok", + "x-api-key": "api-key-tok", + }, + }); + expect(extractToken(req)).toBe("bearer-tok"); + }); +}); + +describe("authenticateRequest", () => { + const kv = env.AUTH_KV; + + it("returns userId for exact token match", async () => { + await kv.put("token:exact-token-123", "usr_abc"); + const req = new Request("https://example.com", { + headers: { Authorization: "Bearer exact-token-123" }, + }); + const result = await authenticateRequest(req, kv); + expect(result).toBe("usr_abc"); + }); + + it("returns null for missing token", async () => { + const req = new Request("https://example.com"); + const result = await authenticateRequest(req, kv); + expect(result).toBeNull(); + }); + + it("returns null for unknown token", async () => { + const req = new Request("https://example.com", { + headers: { Authorization: "Bearer unknown-token" }, + }); + const result = await authenticateRequest(req, kv); + expect(result).toBeNull(); + }); + + it("looks up JWT tokens by SHA-256 hash", async () => { + // Create a fake JWT (3 dot-separated segments) + const fakeJwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.fakesig"; + const hash = await sha256Hex(fakeJwt); + await kv.put(`token:${hash}`, "usr_jwt"); + + const req = new Request("https://example.com", { + headers: { Authorization: `Bearer ${fakeJwt}` }, + }); + const result = await authenticateRequest(req, kv); + expect(result).toBe("usr_jwt"); + }); + + it("falls back to provider-prefix stripping", async () => { + await kv.put("token:realtoken123", "usr_prefix"); + const req = new Request("https://example.com", { + headers: { Authorization: "Bearer sk-ant-api03-realtoken123" }, + }); + const result = await authenticateRequest(req, kv); + expect(result).toBe("usr_prefix"); + }); +}); + +async function sha256Hex(input: string): Promise { + const digest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(input) + ); + return Array.from(new Uint8Array(digest), (b) => + b.toString(16).padStart(2, "0") + ).join(""); +} diff --git a/workers/ai-gateway/test/cors.test.ts b/workers/ai-gateway/test/cors.test.ts new file mode 100644 index 0000000..45a9e1d --- /dev/null +++ b/workers/ai-gateway/test/cors.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from "vitest"; +import { handlePreflight, addCorsHeaders } from "../src/cors"; + +describe("handlePreflight", () => { + it("returns 204 status", () => { + const response = handlePreflight(); + expect(response.status).toBe(204); + }); + + it("includes CORS headers", () => { + const response = handlePreflight(); + expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*"); + expect(response.headers.get("Access-Control-Allow-Methods")).toContain("POST"); + expect(response.headers.get("Access-Control-Allow-Headers")).toContain("Authorization"); + }); + + it("sets max-age header", () => { + const response = handlePreflight(); + expect(response.headers.get("Access-Control-Max-Age")).toBe("86400"); + }); +}); + +describe("addCorsHeaders", () => { + it("adds CORS headers to response", () => { + const original = new Response("body", { status: 200 }); + const patched = addCorsHeaders(original); + expect(patched.headers.get("Access-Control-Allow-Origin")).toBe("*"); + }); + + it("preserves original status", () => { + const original = new Response("error", { status: 500 }); + const patched = addCorsHeaders(original); + expect(patched.status).toBe(500); + }); + + it("preserves body", async () => { + const original = new Response("test body", { status: 200 }); + const patched = addCorsHeaders(original); + expect(await patched.text()).toBe("test body"); + }); +}); diff --git a/workers/ai-gateway/test/errors.test.ts b/workers/ai-gateway/test/errors.test.ts new file mode 100644 index 0000000..b03b676 --- /dev/null +++ b/workers/ai-gateway/test/errors.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import { jsonError } from "../src/errors"; + +describe("jsonError", () => { + it("returns correct status code", () => { + const response = jsonError("Not found", 404); + expect(response.status).toBe(404); + }); + + it("returns JSON content type", () => { + const response = jsonError("Bad request", 400); + expect(response.headers.get("Content-Type")).toBe("application/json"); + }); + + it("returns error object in body", async () => { + const response = jsonError("Something broke", 500); + const body = await response.json(); + expect(body).toEqual({ error: { message: "Something broke" } }); + }); +}); diff --git a/workers/ai-gateway/test/llemtry.test.ts b/workers/ai-gateway/test/llemtry.test.ts new file mode 100644 index 0000000..6709b53 --- /dev/null +++ b/workers/ai-gateway/test/llemtry.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect } from "vitest"; +import { isLlemtryEnabled, isLlmRoute } from "../src/llemtry"; + +// Minimal Log interface for testing +const noopLog = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, +}; + +describe("isLlemtryEnabled", () => { + it("returns false when LLEMTRY_ENABLED is not 'true'", () => { + const env = { + LLEMTRY_ENABLED: "false", + LLEMTRY_ENDPOINT: "https://example.com", + LLEMTRY_AUTH_TOKEN: "token", + } as unknown as Env; + expect(isLlemtryEnabled(env, noopLog)).toBe(false); + }); + + it("returns false when LLEMTRY_ENABLED is undefined", () => { + const env = {} as unknown as Env; + expect(isLlemtryEnabled(env, noopLog)).toBe(false); + }); + + it("returns true when enabled with endpoint and token", () => { + const env = { + LLEMTRY_ENABLED: "true", + LLEMTRY_ENDPOINT: "https://example.com/llemtry", + LLEMTRY_AUTH_TOKEN: "test-token", + } as unknown as Env; + expect(isLlemtryEnabled(env, noopLog)).toBe(true); + }); + + it("returns false when endpoint is missing", () => { + const errorLog = { ...noopLog, error: () => {} }; + const env = { + LLEMTRY_ENABLED: "true", + LLEMTRY_AUTH_TOKEN: "token", + } as unknown as Env; + expect(isLlemtryEnabled(env, errorLog)).toBe(false); + }); + + it("returns false when auth token is missing", () => { + const errorLog = { ...noopLog, error: () => {} }; + const env = { + LLEMTRY_ENABLED: "true", + LLEMTRY_ENDPOINT: "https://example.com", + } as unknown as Env; + expect(isLlemtryEnabled(env, errorLog)).toBe(false); + }); +}); + +describe("isLlmRoute", () => { + it("returns true for anthropic messages route", () => { + expect(isLlmRoute("v1/messages")).toBe(true); + }); + + it("returns true for openai chat completions route", () => { + expect(isLlmRoute("v1/chat/completions")).toBe(true); + }); + + it("returns false for embeddings route", () => { + expect(isLlmRoute("v1/embeddings")).toBe(false); + }); + + it("returns false for models route", () => { + expect(isLlmRoute("v1/models")).toBe(false); + }); +}); diff --git a/workers/ai-gateway/test/log.test.ts b/workers/ai-gateway/test/log.test.ts new file mode 100644 index 0000000..f569f4c --- /dev/null +++ b/workers/ai-gateway/test/log.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect } from "vitest"; +import { sanitizeHeaders, truncateBody } from "../src/log"; + +describe("sanitizeHeaders", () => { + it("redacts authorization header", () => { + const headers = new Headers({ Authorization: "Bearer secret-token-123" }); + const result = sanitizeHeaders(headers); + expect(result["authorization"]).toMatch(/\[REDACTED \(\d+ chars\)\]/); + }); + + it("redacts x-api-key header", () => { + const headers = new Headers({ "x-api-key": "my-api-key" }); + const result = sanitizeHeaders(headers); + expect(result["x-api-key"]).toMatch(/\[REDACTED/); + }); + + it("redacts cookie header", () => { + const headers = new Headers({ Cookie: "session=abc123" }); + const result = sanitizeHeaders(headers); + expect(result["cookie"]).toMatch(/\[REDACTED/); + }); + + it("masks IP addresses (first 6 chars)", () => { + const headers = new Headers({ "cf-connecting-ip": "192.168.1.100" }); + const result = sanitizeHeaders(headers); + expect(result["cf-connecting-ip"]).toBe("192.16…"); + }); + + it("preserves normal headers", () => { + const headers = new Headers({ "Content-Type": "application/json" }); + const result = sanitizeHeaders(headers); + expect(result["content-type"]).toBe("application/json"); + }); + + it("shows length in redacted headers", () => { + const headers = new Headers({ Authorization: "Bearer x" }); + const result = sanitizeHeaders(headers); + expect(result["authorization"]).toBe("[REDACTED (8 chars)]"); + }); + + it("preserves short IP addresses", () => { + const headers = new Headers({ "cf-connecting-ip": "1.2.3" }); + const result = sanitizeHeaders(headers); + expect(result["cf-connecting-ip"]).toBe("1.2.3"); + }); + + it("masks x-forwarded-for", () => { + const headers = new Headers({ "x-forwarded-for": "10.0.0.1, 192.168.1.1" }); + const result = sanitizeHeaders(headers); + expect(result["x-forwarded-for"]).toBe("10.0.0…"); + }); +}); + +describe("truncateBody", () => { + it("returns short body unchanged", () => { + expect(truncateBody("short")).toBe("short"); + }); + + it("truncates long body", () => { + const long = "x".repeat(10000); + const result = truncateBody(long, 100); + expect(result.length).toBeLessThan(long.length); + expect(result).toContain("truncated"); + }); + + it("includes total length in truncation message", () => { + const long = "x".repeat(200); + const result = truncateBody(long, 50); + expect(result).toContain("200 chars total"); + }); +}); diff --git a/workers/ai-gateway/test/routing.test.ts b/workers/ai-gateway/test/routing.test.ts new file mode 100644 index 0000000..ab97e61 --- /dev/null +++ b/workers/ai-gateway/test/routing.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from "vitest"; +import { matchProviderRoute } from "../src/routing"; + +describe("matchProviderRoute", () => { + it("matches anthropic/v1/messages POST", () => { + const result = matchProviderRoute("POST", "/anthropic/v1/messages"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("anthropic"); + expect(result!.directPath).toBe("v1/messages"); + }); + + it("matches openai/v1/chat/completions POST", () => { + const result = matchProviderRoute("POST", "/openai/v1/chat/completions"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai"); + expect(result!.directPath).toBe("v1/chat/completions"); + }); + + it("matches openai/v1/responses POST", () => { + const result = matchProviderRoute("POST", "/openai/v1/responses"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai"); + }); + + it("matches openai/v1/embeddings POST", () => { + const result = matchProviderRoute("POST", "/openai/v1/embeddings"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai"); + }); + + it("matches openai/v1/models GET", () => { + const result = matchProviderRoute("GET", "/openai/v1/models"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai"); + }); + + it("routes openai/v1/codex/responses to openai-codex provider", () => { + const result = matchProviderRoute("POST", "/openai/v1/codex/responses"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai-codex"); + expect(result!.directPath).toBe("codex/responses"); + }); + + it("routes openai-codex/codex/responses to openai-codex provider", () => { + const result = matchProviderRoute("POST", "/openai-codex/codex/responses"); + expect(result).not.toBeNull(); + expect(result!.provider).toBe("openai-codex"); + expect(result!.directPath).toBe("codex/responses"); + }); + + it("returns null for wrong method", () => { + expect(matchProviderRoute("GET", "/anthropic/v1/messages")).toBeNull(); + }); + + it("returns null for unknown path", () => { + expect(matchProviderRoute("POST", "/unknown/path")).toBeNull(); + }); + + it("generates gateway path without /v1/ segment", () => { + const result = matchProviderRoute("POST", "/anthropic/v1/messages"); + expect(result!.gatewayPath).toBe("anthropic/messages"); + }); +}); diff --git a/workers/ai-gateway/vitest.config.ts b/workers/ai-gateway/vitest.config.ts new file mode 100644 index 0000000..f63b42e --- /dev/null +++ b/workers/ai-gateway/vitest.config.ts @@ -0,0 +1,23 @@ +import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { + configPath: "./wrangler.jsonc.example", + }, + miniflare: { + kvNamespaces: ["AUTH_KV"], + bindings: { + LOG_LEVEL: "debug", + ADMIN_AUTH_TOKEN: "test-admin-token", + LLEMTRY_ENABLED: "false", + LLEMTRY_ENDPOINT: "https://example.com/llemtry", + LLEMTRY_AUTH_TOKEN: "test-llemtry-token", + }, + }, + }, + }, + }, +});