diff --git a/.env.example b/.env.example index 10ee5100..3afaa789 100644 --- a/.env.example +++ b/.env.example @@ -1,24 +1,45 @@ -# Next Auth +# Server Configuration +PORT=3001 +HOST=0.0.0.0 +NODE_ENV=development +LOG_LEVEL=info +CORS_ORIGIN=http://localhost:3000 + +# Authentication Configuration # You can generate a new secret on the command line with: -# `npm exec auth secret` -# OR -# `openssl rand -base64 32`` -# https://next-auth.js.org/configuration/options#secret +# `openssl rand -base64 32` AUTH_SECRET= AUTH_URL=http://localhost:3000 -# API token details +# API token details (optional - if not set, no auth required) API_TOKEN='my-api-token' UI_AUTH_EXPIRE_HOURS='2' -# Storage details +# Storage Configuration DATA_STORAGE=fs # could be s3 +DATA_DIR=./data + +# Frontend Configuration (for production) +FRONTEND_DIST=./frontend/dist -# S3 related configuration if DATA_STORAGE is "s3" -S3_ENDPOINT="s3.endpoint", +# S3 Configuration (if DATA_STORAGE is "s3") +S3_ENDPOINT="s3.endpoint" S3_ACCESS_KEY="some_access_key" S3_SECRET_KEY="some_secret_key" S3_PORT=9000 # optional S3_REGION="us-east-1" -S3_BUCKET="bucket_name" # by default "playwright-reports-server" -S3_BATCH_SIZE=10 # by default 10 \ No newline at end of file +S3_BUCKET="playwright-reports-server" # default +S3_BATCH_SIZE=10 # default +S3_MULTIPART_CHUNK_SIZE_MB=25 # default + +# Jira Integration (optional) +JIRA_BASE_URL= +JIRA_EMAIL= +JIRA_API_TOKEN= +JIRA_PROJECT_KEY= + +# Data Cleanup Configuration (optional) +RESULT_EXPIRE_DAYS= +RESULT_EXPIRE_CRON_SCHEDULE="33 3 * * *" +REPORT_EXPIRE_DAYS= +REPORT_EXPIRE_CRON_SCHEDULE="44 4 * * *" \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index af6ab76f..00000000 --- a/.eslintignore +++ /dev/null @@ -1,20 +0,0 @@ -.now/* -*.css -.changeset -dist -esm/* -public/* -tests/* -scripts/* -*.config.js -.DS_Store -node_modules -coverage -.next -build -!.commitlintrc.cjs -!.lintstagedrc.cjs -!jest.config.js -!plopfile.js -!react-shim.js -!tsup.config.ts \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index d2fbabe5..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/eslintrc.json", - "env": { - "browser": false, - "es2021": true, - "node": true - }, - "extends": [ - "plugin:react/recommended", - "plugin:prettier/recommended", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended" - ], - "plugins": ["react", "unused-imports", "import", "@typescript-eslint", "jsx-a11y", "prettier"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 12, - "sourceType": "module" - }, - "settings": { - "react": { - "version": "detect" - } - }, - "rules": { - "no-console": "off", - "react/prop-types": "off", - "react/jsx-uses-react": "off", - "react/react-in-jsx-scope": "off", - "react-hooks/exhaustive-deps": "off", - "jsx-a11y/click-events-have-key-events": "warn", - "jsx-a11y/interactive-supports-focus": "warn", - "prettier/prettier": "warn", - "no-unused-vars": "off", - "unused-imports/no-unused-vars": "off", - "unused-imports/no-unused-imports": "warn", - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "args": "after-used", - "ignoreRestSiblings": false, - "argsIgnorePattern": "^_.*?$" - } - ], - "import/order": [ - "warn", - { - "groups": ["type", "builtin", "object", "external", "internal", "parent", "sibling", "index"], - "pathGroups": [ - { - "pattern": "~/**", - "group": "external", - "position": "after" - } - ], - "newlines-between": "always" - } - ], - "react/self-closing-comp": "warn", - "react/jsx-sort-props": [ - "warn", - { - "callbacksLast": true, - "shorthandFirst": true, - "noSortAlphabetically": false, - "reservedFirst": true - } - ], - "padding-line-between-statements": [ - "warn", - { "blankLine": "always", "prev": "*", "next": "return" }, - { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, - { - "blankLine": "any", - "prev": ["const", "let", "var"], - "next": ["const", "let", "var"] - } - ] - } -} diff --git a/.gitignore b/.gitignore index 93df80f9..bd91d914 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,21 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # data -/data -.tmp +**/data +**/.tmp # dependencies -/node_modules +**/node_modules /.pnp .pnp.js # testing /coverage - -# next.js -.next/ -/out/ +**/test-results # production -/build +**/build +**/dist # misc .DS_Store @@ -38,8 +36,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts -data/reports -data/results # vscode .vscode diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 746669b4..00000000 --- a/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -.npm/ -node_modules/ -.eslintignore -.prettierignore -package.json -package-lock.json diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 957a15d6..00000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 120, - "tabWidth": 2, - "arrowParens": "always", - "endOfLine": "lf" -} diff --git a/Dockerfile b/Dockerfile index 009363db..2c13e833 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,58 @@ FROM node:22-alpine AS base -# Install dependencies only when needed +# Install all dependencies for monorepo FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat WORKDIR /app - -# Install dependencies based on the preferred package manager COPY package.json package-lock.json* ./ RUN npm ci -# Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . - -ARG API_BASE_PATH="" -ENV API_BASE_PATH=$API_BASE_PATH +# Install dependencies for backend +FROM base AS backend-deps +RUN apk add --no-cache libc6-compat -ARG ASSETS_BASE_PATH="" -ENV ASSETS_BASE_PATH=$ASSETS_BASE_PATH +# Install dependencies for frontend -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -ENV NEXT_TELEMETRY_DISABLED=1 +# Build shared package first +FROM base AS shared-builder +WORKDIR /app/packages/shared +COPY --from=deps /app/node_modules ./node_modules +COPY packages/shared/ . +RUN npm run build +# Build frontend +FROM base AS frontend-builder +WORKDIR /app/apps/frontend +COPY --from=deps /app/node_modules ./node_modules +COPY --from=shared-builder /app/packages/shared ./packages/shared +COPY apps/frontend/ . +# Remove the shared dependency from package.json temporarily to avoid npm trying to install it +RUN sed -i '/"@playwright-reports\/shared":/d' package.json +# Create symlink for shared package in node_modules for TypeScript resolution +RUN mkdir -p ./node_modules/@playwright-reports \ + ln -sf ../../packages/shared ./node_modules/@playwright-reports/shared +# Install missing rollup native dependency if needed +RUN npm install @rollup/rollup-linux-arm64-musl --no-save || true +# Skip TypeScript checks and just build with Vite +ENV DOCKER_BUILD=true +RUN npm run build:vite + +# Build backend +FROM base AS backend-builder +WORKDIR /app/apps/backend +COPY --from=deps /app/node_modules ./node_modules +COPY --from=shared-builder /app/packages/shared ./packages/shared +COPY apps/backend/ . +# Create symlink for shared package in node_modules for TypeScript resolution +RUN mkdir -p ./node_modules/@playwright-reports \ + ln -sf ../../packages/shared ./node_modules/@playwright-reports/shared +# Build first with dev dependencies RUN npm run build +# Remove the shared dependency from package.json temporarily to avoid npm trying to install it +RUN sed -i '/"@playwright-reports\/shared":/d' package.json +# Install backend production dependencies (removes dev dependencies) +RUN npm ci --only=production -# Production image, copy all the files and run next +# Production image FROM base AS runner WORKDIR /app @@ -37,22 +60,30 @@ ENV NODE_ENV=production RUN apk add --no-cache curl -# Uncomment the following line in case you want to disable telemetry during runtime. -# ENV NEXT_TELEMETRY_DISABLED 1 - RUN addgroup --system --gid 1001 nodejs && \ - adduser --system --uid 1001 --ingroup nodejs nextjs + adduser --system --uid 1001 --ingroup nodejs appuser + +# Copy all node_modules from deps +COPY --from=deps --chown=appuser:nodejs /app/node_modules ./node_modules -COPY --from=builder --chown=nextjs:nodejs /app/public ./public +# Copy backend node_modules for backend-specific dependencies +COPY --from=backend-builder --chown=appuser:nodejs /app/apps/backend/node_modules ./apps/backend/node_modules -# Set the correct permission for prerender cache -RUN mkdir .next && \ - chown nextjs:nodejs .next +# Copy backend build +COPY --from=backend-builder --chown=appuser:nodejs /app/apps/backend/dist ./apps/backend/dist +COPY --from=backend-builder --chown=appuser:nodejs /app/apps/backend/package.json ./apps/backend/package.json -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +# Copy frontend build +COPY --from=frontend-builder --chown=appuser:nodejs /app/apps/frontend/dist ./apps/frontend/dist + +# Copy shared build +COPY --from=shared-builder --chown=appuser:nodejs /app/packages/shared/dist ./packages/shared/dist + +# Copy public assets +COPY --from=frontend-builder --chown=appuser:nodejs /app/apps/frontend/public ./frontend/public + +# Copy environment configuration (for default values) +COPY --chown=appuser:nodejs .env.example /app/.env.example # Create folders required for storing results and reports ARG DATA_DIR=/app/data @@ -60,16 +91,18 @@ ARG RESULTS_DIR=${DATA_DIR}/results ARG REPORTS_DIR=${DATA_DIR}/reports ARG TEMP_DIR=/app/.tmp RUN mkdir -p ${DATA_DIR} ${RESULTS_DIR} ${REPORTS_DIR} ${TEMP_DIR} && \ - chown -R nextjs:nodejs ${DATA_DIR} ${TEMP_DIR} + chown -R appuser:nodejs ${DATA_DIR} ${TEMP_DIR} + +USER appuser -USER nextjs +EXPOSE 3001 -EXPOSE 3000 +ENV PORT=3001 +ENV FRONTEND_DIST=/app/apps/frontend/dist -ENV PORT=3000 +WORKDIR /app/apps/backend -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["sh", "-c", "HOSTNAME=0.0.0.0 node server.js"] +CMD ["node", "dist/index.js"] -HEALTHCHECK --interval=3m --timeout=3s CMD curl -f http://localhost:$PORT/api/ping || exit 1 +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:$PORT/api/ping || exit 1 \ No newline at end of file diff --git a/app/api/[...nextauth]/route.ts b/app/api/[...nextauth]/route.ts deleted file mode 100644 index bfc6e673..00000000 --- a/app/api/[...nextauth]/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { handlers } from '@/app/auth'; - -export const { GET, POST } = handlers; diff --git a/app/api/config/route.ts b/app/api/config/route.ts deleted file mode 100644 index d5347380..00000000 --- a/app/api/config/route.ts +++ /dev/null @@ -1,187 +0,0 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; - -import { revalidatePath } from 'next/cache'; - -import { withError } from '@/app/lib/withError'; -import { DATA_FOLDER } from '@/app/lib/storage/constants'; -import { service } from '@/app/lib/service'; -import { JiraService } from '@/app/lib/service/jira'; -import { env } from '@/app/config/env'; -import { cronService } from '@/app/lib/service/cron'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -const saveFile = async (file: File) => { - const arrayBuffer = await file.arrayBuffer(); - - const buffer = Buffer.from(arrayBuffer); - - await fs.writeFile(path.join(DATA_FOLDER, file.name), buffer, { encoding: 'binary' }); -}; - -const parseHeaderLinks = async (headerLinks: string): Promise> => { - return JSON.parse(headerLinks); -}; - -export async function PATCH(request: Request) { - const { result: formData, error: formParseError } = await withError(request.formData()); - - if (formParseError) { - return Response.json({ error: formParseError.message }, { status: 400 }); - } - - if (!formData) { - return Response.json({ error: 'Form data is missing' }, { status: 400 }); - } - - const logo = formData.get('logo') as File; - - if (logo) { - const { error: logoError } = await withError(saveFile(logo)); - - if (logoError) { - return Response.json({ error: `failed to save logo: ${logoError?.message}` }, { status: 500 }); - } - } - - const favicon = formData.get('favicon') as File; - - if (favicon) { - const { error: faviconError } = await withError(saveFile(favicon)); - - if (faviconError) { - return Response.json({ error: `failed to save favicon: ${faviconError?.message}` }, { status: 500 }); - } - } - - const title = formData.get('title'); - const logoPath = formData.get('logoPath'); - const faviconPath = formData.get('faviconPath'); - const reporterPaths = formData.get('reporterPaths'); - const headerLinks = formData.get('headerLinks'); - const jiraBaseUrl = formData.get('jiraBaseUrl'); - const jiraEmail = formData.get('jiraEmail'); - const jiraApiToken = formData.get('jiraApiToken'); - const jiraProjectKey = formData.get('jiraProjectKey'); - const resultExpireDays = formData.get('resultExpireDays'); - const resultExpireCronSchedule = formData.get('resultExpireCronSchedule'); - const reportExpireDays = formData.get('reportExpireDays'); - const reportExpireCronSchedule = formData.get('reportExpireCronSchedule'); - - const config = await service.getConfig(); - - if (!config) { - return Response.json({ error: `failed to get config` }, { status: 500 }); - } - - if (title !== null) { - config.title = title.toString(); - } - - if (logo) { - config.logoPath = `/${logo.name}`; - } else if (logoPath !== null) { - config.logoPath = logoPath.toString(); - } - - if (favicon) { - config.faviconPath = `/${favicon.name}`; - } else if (faviconPath !== null) { - config.faviconPath = faviconPath.toString(); - } - - if (reporterPaths !== null) { - try { - config.reporterPaths = JSON.parse(reporterPaths.toString()); - } catch { - config.reporterPaths = [reporterPaths.toString()]; - } - } - - if (headerLinks) { - const { result: parsedHeaderLinks, error: parseHeaderLinksError } = await withError( - parseHeaderLinks(headerLinks.toString()), - ); - - if (parseHeaderLinksError) { - return Response.json( - { error: `failed to parse header links: ${parseHeaderLinksError.message}` }, - { status: 400 }, - ); - } - - if (parsedHeaderLinks) config.headerLinks = parsedHeaderLinks; - } - - if (!config.jira) { - config.jira = {}; - } - - if (jiraBaseUrl !== null) config.jira.baseUrl = jiraBaseUrl.toString(); - if (jiraEmail !== null) config.jira.email = jiraEmail.toString(); - if (jiraApiToken !== null) config.jira.apiToken = jiraApiToken.toString(); - if (jiraProjectKey !== null) config.jira.projectKey = jiraProjectKey.toString(); - - if (jiraBaseUrl || jiraEmail || jiraApiToken || jiraProjectKey) { - JiraService.resetInstance(); - } - - if (!config.cron) { - config.cron = {}; - } - - if (resultExpireDays || resultExpireCronSchedule || reportExpireDays || reportExpireCronSchedule) { - if (resultExpireDays !== null) { - config.cron.resultExpireDays = parseInt(resultExpireDays.toString()); - } - if (resultExpireCronSchedule !== null) { - config.cron.resultExpireCronSchedule = resultExpireCronSchedule.toString(); - } - if (reportExpireDays !== null) { - config.cron.reportExpireDays = parseInt(reportExpireDays.toString()); - } - if (reportExpireCronSchedule !== null) { - config.cron.reportExpireCronSchedule = reportExpireCronSchedule.toString(); - } - } - - const { error: saveConfigError } = await withError(service.updateConfig(config)); - - if (saveConfigError) { - return Response.json({ error: `failed to save config: ${saveConfigError.message}` }, { status: 500 }); - } - - if ( - config.cron?.resultExpireDays || - config.cron?.resultExpireCronSchedule || - config.cron?.reportExpireDays || - config.cron?.reportExpireCronSchedule - ) { - await cronService.restart(); - } - - revalidatePath('/', 'layout'); - revalidatePath('/login', 'layout'); - - return Response.json({ message: 'config saved' }); -} - -export async function GET() { - const config = await service.getConfig(); - - if (!config) { - return Response.json({ error: 'Config not found' }, { status: 404 }); - } - - // Add environment info to config response - const envInfo = { - authRequired: !!env.API_TOKEN, - serverCache: env.USE_SERVER_CACHE, - dataStorage: env.DATA_STORAGE, - s3Endpoint: env.S3_ENDPOINT, - s3Bucket: env.S3_BUCKET, - }; - - return Response.json({ ...config, ...envInfo }, { status: 200 }); -} diff --git a/app/api/info/route.ts b/app/api/info/route.ts deleted file mode 100644 index c1f42c77..00000000 --- a/app/api/info/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET() { - const { result, error } = await withError(service.getServerInfo()); - - if (error) { - return Response.json({ error: error.message }, { status: 500 }); - } - - return Response.json(result); -} diff --git a/app/api/jira/config/route.ts b/app/api/jira/config/route.ts deleted file mode 100644 index bf0e308b..00000000 --- a/app/api/jira/config/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { JiraService } from '@/app/lib/service/jira'; -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; - -export async function GET() { - try { - const config = await service.getConfig(); - const jiraConfig = config.jira; - - const isConfigured = !!(jiraConfig?.baseUrl && jiraConfig?.email && jiraConfig?.apiToken); - - if (!isConfigured) { - return Response.json({ - configured: false, - message: 'Jira is not configured. Please configure Jira settings in the admin panel.', - config: jiraConfig || {}, - }); - } - - const jiraService = JiraService.getInstance(jiraConfig); - - let issueTypes = []; - - if (jiraConfig?.projectKey) { - try { - const project = await jiraService.getProject(jiraConfig.projectKey); - - issueTypes = project.issueTypes || []; - } catch (error) { - console.warn(`Could not fetch project-specific issue types for ${jiraConfig.projectKey}:`, error); - } - } - - return Response.json({ - configured: true, - baseUrl: jiraConfig.baseUrl, - defaultProjectKey: jiraConfig.projectKey, - issueTypes: issueTypes.map((type: any) => ({ - id: type.id, - name: type.name, - description: type.description, - })), - }); - } catch (error) { - return Response.json( - { - configured: false, - error: `Failed to connect to Jira: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - { status: 500 }, - ); - } -} diff --git a/app/api/jira/create-ticket/route.ts b/app/api/jira/create-ticket/route.ts deleted file mode 100644 index 3ddc4b5f..00000000 --- a/app/api/jira/create-ticket/route.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { withError } from '@/app/lib/withError'; -import { JiraService } from '@/app/lib/service/jira'; -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; - -interface CreateTicketRequest { - summary: string; - description: string; - issueType: string; - projectKey: string; - testId: string; - testTitle: string; - testOutcome: string; - testLocation: { - file: string; - line: number; - column: number; - }; - reportId: string; - testAttachments?: Array<{ - name: string; - path: string; - contentType: string; - }>; -} - -export async function POST(request: Request) { - const { result: data, error: parseError } = await withError(request.json()); - - if (parseError) { - return Response.json({ error: parseError.message }, { status: 400 }); - } - - if (!data) { - return Response.json({ error: 'Request data is missing' }, { status: 400 }); - } - - const ticketData = data as CreateTicketRequest; - - try { - const report = await service.getReport(ticketData.reportId); - const projectPath = report.project ? `${report.project}/` : ''; - - ticketData.testAttachments = ticketData.testAttachments?.map((att) => ({ - ...att, - path: `${projectPath}${ticketData.reportId}/${att.path}`, - })); - } catch (error) { - console.error(`Failed to get report ${ticketData.reportId}:`, error); - } - - try { - if (!ticketData.summary || !ticketData.projectKey) { - return Response.json( - { - error: 'Summary and project key are required', - }, - { status: 400 }, - ); - } - - const config = await service.getConfig(); - const jiraService = JiraService.getInstance(config.jira); - - const jiraResponse = await jiraService.createIssue( - ticketData.summary, - ticketData.description, - ticketData.issueType, - ticketData.projectKey, - { - testId: ticketData.testId, - testTitle: ticketData.testTitle, - testOutcome: ticketData.testOutcome, - testLocation: ticketData.testLocation, - }, - ticketData.testAttachments, - ); - - return Response.json( - { - success: true, - issueKey: jiraResponse.key, - issueId: jiraResponse.id, - issueUrl: jiraResponse.self, - message: 'Jira ticket created successfully', - data: { - ...ticketData, - issueKey: jiraResponse.key, - issueId: jiraResponse.id, - issueUrl: jiraResponse.self, - created: new Date().toISOString(), - }, - }, - { status: 201 }, - ); - } catch (error) { - console.error('Failed to create Jira ticket:', error); - - if (error instanceof Error && error.message.includes('Jira configuration is incomplete')) { - return Response.json( - { - error: - 'Jira is not configured. Please set up JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.', - }, - { status: 500 }, - ); - } - - return Response.json( - { - error: `Failed to create Jira ticket: ${error instanceof Error ? error.message : 'Unknown error'}`, - }, - { status: 500 }, - ); - } -} diff --git a/app/api/ping/route.ts b/app/api/ping/route.ts deleted file mode 100644 index 24d07811..00000000 --- a/app/api/ping/route.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET() { - return new Response('pong'); -} diff --git a/app/api/report/[id]/route.ts b/app/api/report/[id]/route.ts deleted file mode 100644 index 427d9296..00000000 --- a/app/api/report/[id]/route.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { type NextRequest } from 'next/server'; - -import { withError } from '@/app/lib/withError'; -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET( - req: NextRequest, - { - params, - }: { - params: { - id: string; - }; - }, -) { - const { id } = params; - - if (!id) { - return new Response('report ID is required', { status: 400 }); - } - - const { result: report, error } = await withError(service.getReport(id)); - - if (error) { - return new Response(`failed to get report: ${error?.message ?? 'unknown error'}`, { status: 400 }); - } - - return Response.json(report); -} diff --git a/app/api/report/delete/route.ts b/app/api/report/delete/route.ts deleted file mode 100644 index e9656991..00000000 --- a/app/api/report/delete/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto -export async function DELETE(request: Request) { - const { result: reqData, error: reqError } = await withError(request.json()); - - if (reqError) { - return new Response(reqError.message, { status: 400 }); - } - - const { error } = await withError(service.deleteReports(reqData.reportsIds)); - - if (error) { - return new Response(error.message, { status: 404 }); - } - - return Response.json({ - message: `Reports deleted successfully`, - reportsIds: reqData.reportsIds, - }); -} diff --git a/app/api/report/generate/route.ts b/app/api/report/generate/route.ts deleted file mode 100644 index afb8c379..00000000 --- a/app/api/report/generate/route.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function POST(request: Request) { - const { result: reqBody, error: reqError } = await withError(request.json()); - - if (reqError) { - return new Response(reqError.message, { status: 400 }); - } - const { resultsIds, project, playwrightVersion, ...rest } = reqBody; - - try { - const result = await service.generateReport(resultsIds, { project, playwrightVersion, ...rest }); - - if (!result?.reportId) { - return new Response('failed to generate report', { status: 400 }); - } - - return Response.json(result); - } catch (error) { - console.error(`[report/generate] error: ${error}`); - if (error instanceof Error && error.message.includes('ENOENT: no such file or directory')) { - return Response.json({ error: `ResultID with not found: ${error.message}` }, { status: 404 }); - } - - return Response.json({ error: (error as Error).message }, { status: 500 }); - } -} diff --git a/app/api/report/list/route.ts b/app/api/report/list/route.ts deleted file mode 100644 index ad216c87..00000000 --- a/app/api/report/list/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { type NextRequest } from 'next/server'; - -import { withError } from '@/app/lib/withError'; -import { parseFromRequest } from '@/app/lib/storage/pagination'; -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url); - const pagination = parseFromRequest(searchParams); - const project = searchParams.get('project') ?? ''; - const search = searchParams.get('search') ?? ''; - - const { result: reports, error } = await withError(service.getReports({ pagination, project, search })); - - if (error) { - return new Response(error.message, { status: 400 }); - } - - return Response.json(reports!); -} diff --git a/app/api/report/projects/route.ts b/app/api/report/projects/route.ts deleted file mode 100644 index 836cd7a1..00000000 --- a/app/api/report/projects/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET() { - const { result: projects, error } = await withError(service.getReportsProjects()); - - if (error) { - return new Response(error.message, { status: 400 }); - } - - return Response.json(projects); -} diff --git a/app/api/report/trend/route.ts b/app/api/report/trend/route.ts deleted file mode 100644 index 1bda6a47..00000000 --- a/app/api/report/trend/route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type NextRequest } from 'next/server'; - -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url); - const project = searchParams.get('project') ?? ''; - const { reports } = await service.getReports({ project }); - - const latestReports = reports.slice(0, 20); - - return Response.json(latestReports); -} diff --git a/app/api/result/delete/route.ts b/app/api/result/delete/route.ts deleted file mode 100644 index 46eda0d4..00000000 --- a/app/api/result/delete/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function DELETE(request: Request) { - const { result: reqData, error: reqError } = await withError(request.json()); - - if (reqError) { - return new Response(reqError.message, { status: 400 }); - } - - const { error } = await withError(service.deleteResults(reqData.resultsIds)); - - if (error) { - return new Response(error.message, { status: 404 }); - } - - return Response.json({ - message: `Results files deleted successfully`, - resultsIds: reqData.resultsIds, - }); -} diff --git a/app/api/result/list/route.ts b/app/api/result/list/route.ts deleted file mode 100644 index 24515fb7..00000000 --- a/app/api/result/list/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { type NextRequest } from 'next/server'; - -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; -import { parseFromRequest } from '@/app/lib/storage/pagination'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url); - const pagination = parseFromRequest(searchParams); - const project = searchParams.get('project') ?? ''; - const tags = searchParams.get('tags')?.split(',').filter(Boolean) ?? []; - const search = searchParams.get('search') ?? ''; - - const { result, error } = await withError(service.getResults({ pagination, project, tags, search })); - - if (error) { - return new Response(error.message, { status: 400 }); - } - - return Response.json(result); -} diff --git a/app/api/result/projects/route.ts b/app/api/result/projects/route.ts deleted file mode 100644 index 1548a20e..00000000 --- a/app/api/result/projects/route.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { service } from '@/app/lib/service'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET() { - const { result: projects, error } = await withError(service.getResultsProjects()); - - if (error) { - return new Response(error.message, { status: 400 }); - } - - return Response.json(projects); -} diff --git a/app/api/result/tags/route.ts b/app/api/result/tags/route.ts deleted file mode 100644 index 1439896e..00000000 --- a/app/api/result/tags/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { NextRequest } from 'next/server'; - -import { withError } from '@/app/lib/withError'; -import { service } from '@/app/lib/service'; - -export const dynamic = 'force-dynamic'; - -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url); - const project = searchParams.get('project') ?? ''; - - const { result: tags, error } = await withError(service.getResultsTags(project)); - - if (error) { - return new Response(error.message, { status: 400 }); - } - - return Response.json(tags); -} diff --git a/app/api/serve/[[...filePath]]/route.ts b/app/api/serve/[[...filePath]]/route.ts deleted file mode 100644 index ad7e4332..00000000 --- a/app/api/serve/[[...filePath]]/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -import path from 'path'; - -import mime from 'mime'; -import { type NextRequest, NextResponse } from 'next/server'; -import { redirect } from 'next/navigation'; - -import { withError } from '@/app/lib/withError'; -import { storage } from '@/app/lib/storage'; -import { auth } from '@/app/auth'; -import { env } from '@/app/config/env'; -import { withBase } from '@/app/lib/url'; - -interface ReportParams { - reportId: string; - filePath?: string[]; -} - -export async function GET( - req: NextRequest, - { - params, - }: { - params: ReportParams; - }, -) { - // is not protected by the middleware - // as we want to have callbackUrl in the query - - const authRequired = !!env.API_TOKEN; - const session = await auth(); - - const { filePath } = params; - - const uriPath = Array.isArray(filePath) ? filePath.join('/') : (filePath ?? ''); - - const targetPath = decodeURI(uriPath); - - // Only check for session if auth is required - if (authRequired && !session?.user?.jwtToken) { - redirect(withBase(`/login?callbackUrl=${encodeURI(req.nextUrl.pathname)}`)); - } - - const contentType = mime.getType(path.basename(targetPath)); - - if (!contentType && !path.extname(targetPath)) { - return NextResponse.next(); - } - - const { result: content, error } = await withError(storage.readFile(targetPath, contentType)); - - if (error ?? !content) { - return NextResponse.json({ error: `Could not read file ${error?.message ?? ''}` }, { status: 404 }); - } - - const headers = { - headers: { - 'Content-Type': contentType ?? 'application/octet-stream', - Authorization: `Bearer ${session?.user?.apiToken}`, - }, - }; - - return new Response(content, headers); -} diff --git a/app/api/static/[[...filePath]]/route.ts b/app/api/static/[[...filePath]]/route.ts deleted file mode 100644 index 28ea2306..00000000 --- a/app/api/static/[[...filePath]]/route.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from 'node:path'; -import fs from 'node:fs/promises'; - -import mime from 'mime'; -import { type NextRequest, NextResponse } from 'next/server'; - -import { DATA_FOLDER } from '@/app/lib/storage/constants'; -import { withError } from '@/app/lib/withError'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -interface ServeParams { - filePath?: string[]; -} - -export async function GET( - _: NextRequest, - { - params, - }: { - params: ServeParams; - }, -) { - const { filePath } = params; - - const uriPath = Array.isArray(filePath) ? filePath.join('/') : (filePath ?? ''); - - const targetPath = decodeURI(uriPath); - - const contentType = mime.getType(path.basename(targetPath)); - - if (!contentType && !path.extname(targetPath)) { - return NextResponse.next(); - } - - const imageDataPath = path.join(DATA_FOLDER, targetPath); - const imagePublicPath = path.join('public', targetPath); - - const { error: dataAccessError } = await withError(fs.access(imageDataPath)); - - const imagePath = dataAccessError ? imagePublicPath : imageDataPath; - - const imageBuffer = await fs.readFile(imagePath); - - const headers = { - headers: { - 'Content-Type': contentType ?? 'image/*', - }, - }; - - return new Response(imageBuffer, headers); -} diff --git a/app/auth.ts b/app/auth.ts deleted file mode 100644 index a4427e6d..00000000 --- a/app/auth.ts +++ /dev/null @@ -1,112 +0,0 @@ -import NextAuth from 'next-auth'; -import { NextAuthConfig } from 'next-auth'; -import { type User } from 'next-auth'; -import CredentialsProvider from 'next-auth/providers/credentials'; -import jwt from 'jsonwebtoken'; - -import { env } from './config/env'; - -const useAuth = !!env.API_TOKEN; - -// strictly recommended to specify via env var -// Use a stable default secret when AUTH_SECRET is not set to avoid JWT decryption errors -// This is only acceptable when auth is disabled (no API_TOKEN) -const secret = env.AUTH_SECRET ?? 'default-secret-for-non-auth-mode'; - -// session expiration for api token auth -const expirationHours = env.UI_AUTH_EXPIRE_HOURS ? parseInt(env.UI_AUTH_EXPIRE_HOURS) : 2; -const expirationSeconds = expirationHours * 60 * 60; - -export const authConfig: NextAuthConfig = { - secret, - providers: [ - CredentialsProvider({ - name: 'API Token', - credentials: { - apiToken: { label: 'API Token', type: 'password' }, - }, - async authorize(credentials): Promise { - if (credentials?.apiToken === env.API_TOKEN) { - const token = jwt.sign({ authorized: true }, secret); - - return { - apiToken: credentials.apiToken as string, - jwtToken: token, - }; - } - - return null; - }, - }), - ], - callbacks: { - async jwt({ token, user }) { - if (user) { - token.apiToken = user.apiToken; - token.jwtToken = user.jwtToken; - } - - return token; - }, - async session({ session, token }) { - session.user.apiToken = token.apiToken as string; - session.user.jwtToken = token.jwtToken as string; - - return session; - }, - }, - session: { - strategy: 'jwt', - maxAge: expirationSeconds, - }, - trustHost: true, - pages: { - signIn: '/login', - }, -}; - -const getJwtStubToken = () => { - return jwt.sign({ authorized: true }, secret); -}; - -const noAuth = { - providers: [ - CredentialsProvider({ - name: 'No Auth', - credentials: {}, - async authorize() { - const token = getJwtStubToken(); - - return { apiToken: token, jwtToken: token }; - }, - }), - ], - callbacks: { - authorized: async () => { - return true; - }, - async jwt({ token, user }) { - if (user) { - token.apiToken = user.apiToken; - token.jwtToken = user.jwtToken; - } - - return token; - }, - async session({ session, token }) { - session.sessionToken = getJwtStubToken(); - session.user.jwtToken = session.sessionToken; - session.user.apiToken = token.apiToken as string; - - return session; - }, - }, - trustHost: true, - session: { - strategy: 'jwt', - maxAge: expirationSeconds, - }, - secret, -} satisfies NextAuthConfig; - -export const { handlers, auth, signIn, signOut } = NextAuth(useAuth ? authConfig : noAuth); diff --git a/app/components/page-layout.tsx b/app/components/page-layout.tsx deleted file mode 100644 index 6b621161..00000000 --- a/app/components/page-layout.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { useLayoutEffect, useState, useEffect } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { useSession } from 'next-auth/react'; -import { Spinner } from '@heroui/react'; -import { toast } from 'sonner'; - -import useQuery from '@/app/hooks/useQuery'; -import { useAuthConfig } from '@/app/hooks/useAuthConfig'; -import { type ServerDataInfo } from '@/app/lib/storage'; - -interface PageLayoutProps { - render: (props: { info: ServerDataInfo; onUpdate: () => void }) => React.ReactNode; -} - -export default function PageLayout({ render }: PageLayoutProps) { - const { data: session, status } = useSession(); - const authIsLoading = status === 'loading'; - const { authRequired } = useAuthConfig(); - - const { data: info, error, refetch, isLoading: isInfoLoading } = useQuery('/api/info'); - const [refreshId, setRefreshId] = useState(uuidv4()); - - useEffect(() => { - // Only show error if auth is required - if (authRequired === false) { - return; - } - - if (!authIsLoading && !session && authRequired === true) { - toast.error('You are not authenticated'); - } - }, [authIsLoading, session, authRequired]); - - useLayoutEffect(() => { - // Skip session check if auth is not required - if (authRequired === false) { - refetch(); - - return; - } - - if (authIsLoading || !session) { - return; - } - refetch(); - }, [refreshId, session, authRequired]); - - if (authIsLoading || isInfoLoading) { - return ; - } - - const updateRefreshId = () => { - setRefreshId(uuidv4()); - }; - - if (error) { - toast.error(error.message); - - return
Error loading data: {error.message}
; - } - - return ( - <> - {!!info && ( -
-
{render({ info, onUpdate: updateRefreshId })}
-
- )} - - ); -} diff --git a/app/components/report-details/report-stats.tsx b/app/components/report-details/report-stats.tsx deleted file mode 100644 index b37d0883..00000000 --- a/app/components/report-details/report-stats.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC } from 'react'; - -import { StatChart } from '../stat-chart'; - -import { type ReportStats } from '@/app/lib/parser'; -import { pluralize } from '@/app/lib/transformers'; - -interface StatisticsProps { - stats?: ReportStats; -} - -const ReportStatistics: FC = ({ stats }) => { - if (!stats || Object.keys(stats).length === 0) { - return
No statistics available
; - } - - return ( -
-

- Total: {stats.total} {pluralize(stats.total, 'test', 'tests')} -

- -
- ); -}; - -export default ReportStatistics; diff --git a/app/components/report-details/test-info.tsx b/app/components/report-details/test-info.tsx deleted file mode 100644 index dd312c65..00000000 --- a/app/components/report-details/test-info.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { FC } from 'react'; -import { Link, LinkIcon, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@heroui/react'; - -import FormattedDate from '../date-format'; - -import { subtitle } from '@/app/components/primitives'; -import { parseMilliseconds } from '@/app/lib/time'; -import { type TestHistory, type ReportHistory } from '@/app/lib/storage'; -import { type ReportTest } from '@/app/lib/parser/types'; -import { testStatusToColor } from '@/app/lib/tailwind'; -import { withBase } from '@/app/lib/url'; - -interface TestInfoProps { - history: ReportHistory[]; - test: ReportTest; -} - -const getTestHistory = (testId: string, history: ReportHistory[]) => { - return history - .map((report) => { - const file = report?.files?.find((file) => file.tests.some((test) => test.testId === testId)); - - if (!file) { - return; - } - - const test = file.tests.find((test) => test.testId === testId); - - if (!test) { - return; - } - - return { - ...test, - createdAt: report.createdAt, - reportID: report.reportID, - reportUrl: report.reportUrl, - }; - }) - .filter(Boolean) as unknown as TestHistory[]; -}; - -const TestInfo: FC = ({ test, history }: TestInfoProps) => { - const formatted = testStatusToColor(test.outcome); - - const testHistory = getTestHistory(test.testId, history); - - return ( -
-
-

- Outcome: {formatted.title} -

-

Location: {`${test.location.file}:${test.location.line}:${test.location.column}`}

-

Duration: {parseMilliseconds(test.duration)}

- {test.annotations.length > 0 &&

Annotations: {test.annotations.map((a) => JSON.stringify(a)).join(', ')}

} - {test.tags.length > 0 &&

Tags: {test.tags.join(', ')}

} -
- {!!testHistory?.length && ( -
-

Results:

- - - Created At - Status - Duration - Actions - - - {(item) => { - const itemOutcome = testStatusToColor(item?.outcome); - - return ( - - - - - - {itemOutcome.title} - - {parseMilliseconds(item.duration)} - - - - - - - ); - }} - -
-
- )} -
- ); -}; - -export default TestInfo; diff --git a/app/components/results.tsx b/app/components/results.tsx deleted file mode 100644 index 88bdf081..00000000 --- a/app/components/results.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -import { type Result } from '@/app/lib/storage'; -import ResultsTable from '@/app/components/results-table'; -import { title } from '@/app/components/primitives'; -import GenerateReportButton from '@/app/components/generate-report-button'; -import DeleteResultsButton from '@/app/components/delete-results-button'; -import UploadResultsButton from '@/app/components/upload-results-button'; -import { getUniqueProjectsList } from '@/app/lib/storage/format'; - -interface ResultsProps { - onChange: () => void; -} - -export default function Results({ onChange }: Readonly) { - const [selectedResults, setSelectedResults] = useState([]); - - const selectedResultIds = selectedResults.map((r) => r.resultID); - - const projects = getUniqueProjectsList(selectedResults); - - const onListUpdate = () => { - setSelectedResults([]); - onChange?.(); - }; - - return ( - <> -
-
-

Results

-
-
- {selectedResults.length > 0 && ( -
Results selected: {selectedResults.length}
- )} - - - -
-
-
- - - ); -} diff --git a/app/config/fonts.ts b/app/config/fonts.ts deleted file mode 100644 index ebf5336e..00000000 --- a/app/config/fonts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Fira_Code as FontMono, Inter as FontSans } from 'next/font/google'; - -export const fontSans = FontSans({ - subsets: ['latin'], - variable: '--font-sans', -}); - -export const fontMono = FontMono({ - subsets: ['latin'], - variable: '--font-mono', -}); diff --git a/app/config/runtime.ts b/app/config/runtime.ts deleted file mode 100644 index 8ed6a6c0..00000000 --- a/app/config/runtime.ts +++ /dev/null @@ -1 +0,0 @@ -export const isBuildStage = process.env.NEXT_PHASE === 'phase-production-build'; diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index d47239a8..00000000 --- a/app/layout.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import '@/app/styles/globals.css'; -import { Metadata, Viewport } from 'next'; -import Link from 'next/link'; -import clsx from 'clsx'; -import { Toaster } from 'sonner'; - -import { Providers } from './providers'; - -import { siteConfig } from '@/app/config/site'; -import { fontSans } from '@/app/config/fonts'; -import { Navbar } from '@/app/components/navbar'; -import { Aside } from '@/app/components/aside'; -import { service } from '@/app/lib/service'; -import { withBase } from '@/app/lib/url'; - -export async function generateMetadata(): Promise { - const config = await service.getConfig(); - - return { - title: { - default: siteConfig.name, - template: `%s - ${siteConfig.name}`, - }, - description: siteConfig.description, - icons: { - icon: withBase(config?.faviconPath ? `/api/static${config.faviconPath}` : '/favicon.ico'), - }, - }; -} - -export async function generateViewport(): Promise { - return { - themeColor: [ - { media: '(prefers-color-scheme: light)', color: 'white' }, - { media: '(prefers-color-scheme: dark)', color: 'black' }, - ], - }; -} - -export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { - return ( - - - - - - - - - -
- -
-
-
- - Powered by -

CyborgTests

- -
-
-
- - - ); -} diff --git a/app/lib/query-cache.ts b/app/lib/query-cache.ts deleted file mode 100644 index ecd92b0a..00000000 --- a/app/lib/query-cache.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type QueryClient } from '@tanstack/react-query'; - -interface InvalidateCacheOptions { - queryKeys?: string[]; - predicate?: string; -} - -export const invalidateCache = (client: QueryClient, options: InvalidateCacheOptions) => { - if (options?.queryKeys) { - client.invalidateQueries({ queryKey: options.queryKeys }); - } - - if (options?.predicate) { - client.invalidateQueries({ - predicate: (q) => q.queryKey.some((key) => typeof key === 'string' && key.startsWith(options.predicate!)), - }); - } -}; diff --git a/app/lib/service/cache/index.ts b/app/lib/service/cache/index.ts deleted file mode 100644 index 8218c98b..00000000 --- a/app/lib/service/cache/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './reports'; -export * from './results'; -export * from './config'; diff --git a/app/lib/service/cache/reports.ts b/app/lib/service/cache/reports.ts deleted file mode 100644 index 470fac2e..00000000 --- a/app/lib/service/cache/reports.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { withError } from '../../withError'; - -import { storage } from '@/app/lib/storage'; -import { type ReportHistory } from '@/app/lib/storage/types'; -import { env } from '@/app/config/env'; - -type ReportsMap = Map; - -export class ReportCache { - private static instance: ReportCache; - public initialized = false; - private readonly reports: ReportsMap; - - private constructor() { - this.reports = new Map(); - } - - public static getInstance() { - if (!ReportCache.instance) { - ReportCache.instance = new ReportCache(); - } - - return ReportCache.instance; - } - - public async init() { - if (this.initialized || !env.USE_SERVER_CACHE) { - return; - } - - console.log('[report cache] initializing cache'); - const { result, error } = await withError(storage.readReports()); - - if (error) { - console.error('[report cache] failed to read reports:', error); - - return; - } - - if (!result?.reports?.length) { - return; - } - - for (const report of result.reports) { - ReportCache.getInstance().reports.set(report.reportID, report); - } - - this.initialized = true; - } - - public onDeleted(reportIds: string[]) { - if (!env.USE_SERVER_CACHE) { - return; - } - - for (const id of reportIds) { - this.reports.delete(id); - } - } - - public onCreated(report: ReportHistory) { - if (!env.USE_SERVER_CACHE) { - return; - } - this.reports.set(report.reportID, report); - } - - public getAll(): ReportHistory[] { - return Array.from(this.reports.values()); - } - - public getByID(reportID: string): ReportHistory | undefined { - return this.reports.get(reportID); - } -} - -export const reportCache = ReportCache.getInstance(); diff --git a/app/lib/service/cache/results.ts b/app/lib/service/cache/results.ts deleted file mode 100644 index e39e2c10..00000000 --- a/app/lib/service/cache/results.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { withError } from '../../withError'; - -import { storage } from '@/app/lib/storage'; -import { type Result } from '@/app/lib/storage/types'; -import { env } from '@/app/config/env'; - -type ResultsMap = Map; - -export class ResultCache { - private static instance: ResultCache; - public initialized = false; - private readonly results: ResultsMap; - - private constructor() { - this.results = new Map(); - } - - public static getInstance() { - if (!ResultCache.instance) { - ResultCache.instance = new ResultCache(); - } - - return ResultCache.instance; - } - - public async init() { - if (this.initialized || !env.USE_SERVER_CACHE) { - return; - } - - console.log('[result cache] initializing cache'); - const { result: resultsResponse, error } = await withError(storage.readResults()); - - if (error) { - console.error('[result cache] failed to read results:', error); - - return; - } - - if (!resultsResponse?.results?.length) { - return; - } - - const cache = ResultCache.getInstance(); - - for (const result of resultsResponse.results) { - cache.results.set(result.resultID, result); - } - - this.initialized = true; - } - - public onDeleted(resultIds: string[]) { - if (!env.USE_SERVER_CACHE) { - return; - } - - for (const id of resultIds) { - this.results.delete(id); - } - } - - public onCreated(result: Result) { - if (!env.USE_SERVER_CACHE) { - return; - } - - this.results.set(result.resultID, result); - } - - public getAll(): Result[] { - return Array.from(this.results.values()); - } - - public getByID(resultID: string): Result | undefined { - return this.results.get(resultID); - } -} - -export const resultCache = ResultCache.getInstance(); diff --git a/app/lib/service/index.ts b/app/lib/service/index.ts deleted file mode 100644 index 8a922a3d..00000000 --- a/app/lib/service/index.ts +++ /dev/null @@ -1,429 +0,0 @@ -import { PassThrough, Readable } from 'node:stream'; - -import { withError } from '../withError'; -import { bytesToString, getUniqueProjectsList } from '../storage/format'; -import { serveReportRoute } from '../constants'; -import { DEFAULT_STREAM_CHUNK_SIZE } from '../storage/constants'; - -import { lifecycle } from '@/app/lib/service/lifecycle'; -import { configCache, reportCache, resultCache } from '@/app/lib/service/cache'; -import { - type ReadReportsInput, - ReadResultsInput, - ReadResultsOutput, - ReportHistory, - ReportMetadata, - ResultDetails, - ServerDataInfo, - isReportHistory, - storage, -} from '@/app/lib/storage'; -import { handlePagination } from '@/app/lib/storage/pagination'; -import { SiteWhiteLabelConfig } from '@/app/types'; -import { defaultConfig } from '@/app/lib/config'; -import { env } from '@/app/config/env'; -import { type S3 } from '@/app/lib/storage/s3'; -import { isValidPlaywrightVersion } from '@/app/lib/pw'; - -class Service { - private static instance: Service; - - public static getInstance() { - console.log(`[service] get instance`); - if (!Service.instance) { - Service.instance = new Service(); - } - - return Service.instance; - } - - private shouldUseServerCache(): boolean { - return env.USE_SERVER_CACHE && lifecycle.isInitialized(); - } - - public async getReports(input?: ReadReportsInput) { - console.log(`[service] getReports`); - const cached = this.shouldUseServerCache() && reportCache.initialized ? reportCache.getAll() : []; - - const shouldUseCache = !input?.ids; - - if (cached.length && shouldUseCache) { - console.log(`[service] using cached reports`); - const noFilters = !input?.project && !input?.ids; - const shouldFilterByProject = (report: ReportHistory) => input?.project && report.project === input.project; - const shouldFilterByID = (report: ReportHistory) => input?.ids?.includes(report.reportID); - - let reports = cached.filter((report) => noFilters || shouldFilterByProject(report) || shouldFilterByID(report)); - - // Filter by search if provided - if (input?.search?.trim()) { - const searchTerm = input.search.toLowerCase().trim(); - - reports = reports.filter((report) => { - // Search in title, reportID, project, and all metadata fields - const searchableFields = [ - report.title, - report.reportID, - report.project, - ...Object.entries(report) - .filter( - ([key]) => - !['reportID', 'title', 'createdAt', 'size', 'sizeBytes', 'project', 'reportUrl', 'stats'].includes( - key, - ), - ) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - - reports.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); - const currentReports = handlePagination(reports, input?.pagination); - - return { - reports: currentReports, - total: reports.length, - }; - } - - console.log(`[service] using external reports`); - - return await storage.readReports(input); - } - - public async getReport(id: string): Promise { - console.log(`[service] getReport ${id}`); - const cached = this.shouldUseServerCache() && reportCache.initialized ? reportCache.getByID(id) : undefined; - - if (isReportHistory(cached)) { - console.log(`[service] using cached report`); - - return cached; - } - - console.log(`[service] fetching report`); - - const { reports } = await this.getReports({ ids: [id] }); - - const report = reports.find((report) => report.reportID === id); - - if (!report) { - throw new Error(`report with id ${id} not found`); - } - - return report; - } - - private async findLatestPlaywrightVersionFromResults(resultIds: string[]) { - for (const resultId of resultIds) { - const { result: results, error } = await withError(this.getResults({ search: resultId })); - - if (error || !results) { - continue; - } - - const [latestResult] = results.results; - - if (!latestResult) { - continue; - } - - const latestVersion = latestResult?.playwrightVersion; - - if (latestVersion) { - return latestVersion; - } - } - } - - private async findLatestPlaywrightVersion(resultIds: string[]) { - const versionFromResults = await this.findLatestPlaywrightVersionFromResults(resultIds); - - if (versionFromResults) { - return versionFromResults; - } - - // just in case version not found in results, we can try to get it from latest reports - const { result: reportsArray, error } = await withError(this.getReports({ pagination: { limit: 10, offset: 0 } })); - - if (error || !reportsArray) { - return ''; - } - - const reportWithVersion = reportsArray.reports.find((report) => !!report.metadata?.playwrightVersion); - - if (!reportWithVersion) { - return ''; - } - - return reportWithVersion.metadata.playwrightVersion; - } - - public async generateReport( - resultsIds: string[], - metadata?: ReportMetadata, - ): Promise<{ reportId: string; reportUrl: string; metadata: ReportMetadata }> { - const version = isValidPlaywrightVersion(metadata?.playwrightVersion) - ? metadata?.playwrightVersion - : await this.findLatestPlaywrightVersion(resultsIds); - - const metadataWithVersion = { ...(metadata ?? {}), playwrightVersion: version ?? '' }; - - const reportId = await storage.generateReport(resultsIds, metadataWithVersion); - - const report = await this.getReport(reportId); - - reportCache.onCreated(report); - - const projectPath = metadata?.project ? `${encodeURI(metadata.project)}/` : ''; - const reportUrl = `${serveReportRoute}/${projectPath}${reportId}/index.html`; - - return { reportId, reportUrl, metadata: metadataWithVersion }; - } - - public async deleteReports(reportIDs: string[]) { - const { error } = await withError(storage.deleteReports(reportIDs)); - - if (error) { - throw error; - } - - reportCache.onDeleted(reportIDs); - } - - public async getReportsProjects(): Promise { - const { reports } = await this.getReports(); - const projects = getUniqueProjectsList(reports); - - return projects; - } - - public async getResults(input?: ReadResultsInput): Promise { - console.log(`[results service] getResults`); - const cached = this.shouldUseServerCache() && resultCache.initialized ? resultCache.getAll() : []; - - if (!cached.length) { - return await storage.readResults(input); - } - - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - - cached.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); - - let filtered = input?.project - ? cached.filter((file) => (input?.project ? file.project === input.project : file)) - : cached; - - if (input?.testRun) { - filtered = filtered.filter((file) => file.testRun === input.testRun); - } - - // Filter by tags if provided - if (input?.tags && input.tags.length > 0) { - const notMetadataKeys = ['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project']; - - filtered = filtered.filter((result) => { - const resultTags = Object.entries(result) - .filter(([key]) => !notMetadataKeys.includes(key)) - .map(([key, value]) => `${key}: ${value}`); - - return input.tags!.some((selectedTag) => resultTags.includes(selectedTag)); - }); - } - - // Filter by search if provided - if (input?.search?.trim()) { - const searchTerm = input.search.toLowerCase().trim(); - - filtered = filtered.filter((result) => { - // Search in title, resultID, project, and all metadata fields - const searchableFields = [ - result.title, - result.resultID, - result.project, - ...Object.entries(result) - .filter(([key]) => !['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project'].includes(key)) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const results = !input?.pagination ? filtered : handlePagination(filtered, input?.pagination); - - return { - results, - total: filtered.length, - }; - } - - public async deleteResults(resultIDs: string[]): Promise { - const { error } = await withError(storage.deleteResults(resultIDs)); - - if (error) { - throw error; - } - - resultCache.onDeleted(resultIDs); - } - - public async getPresignedUrl(fileName: string): Promise { - console.log(`[service] getPresignedUrl for ${fileName}`); - - if (env.DATA_STORAGE !== 's3') { - console.log(`[service] fs storage detected, no presigned URL needed`); - - return ''; - } - - console.log(`[service] s3 detected, generating presigned URL`); - - const { result: presignedUrl, error } = await withError((storage as S3).generatePresignedUploadUrl(fileName)); - - if (error) { - console.error(`[service] getPresignedUrl | error: ${error.message}`); - - return ''; - } - - return presignedUrl!; - } - - public async saveResult(filename: string, stream: PassThrough, presignedUrl?: string, contentLength?: string) { - if (!presignedUrl) { - console.log(`[service] saving result`); - - return await storage.saveResult(filename, stream); - } - - console.log(`[service] using direct upload via presigned URL`, presignedUrl); - - const { error } = await withError( - fetch(presignedUrl, { - method: 'PUT', - body: Readable.toWeb(stream, { - strategy: { - highWaterMark: DEFAULT_STREAM_CHUNK_SIZE, - }, - }), - headers: { - 'Content-Type': 'application/zip', - 'Content-Length': contentLength, - }, - duplex: 'half', - } as RequestInit), - ); - - if (error) { - console.error(`[s3] saveResult | error: ${error.message}`); - throw error; - } - } - - public async saveResultDetails(resultID: string, resultDetails: ResultDetails, size: number) { - const result = await storage.saveResultDetails(resultID, resultDetails, size); - - resultCache.onCreated(result); - - return result; - } - - public async getResultsProjects(): Promise { - const { results } = await this.getResults(); - const projects = getUniqueProjectsList(results); - - const reportProjects = await this.getReportsProjects(); - - return Array.from(new Set([...projects, ...reportProjects])); - } - - public async getResultsTags(project?: string): Promise { - const { results } = await this.getResults(project ? { project } : undefined); - - const notMetadataKeys = ['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project']; - const allTags = new Set(); - - results.forEach((result) => { - Object.entries(result).forEach(([key, value]) => { - if (!notMetadataKeys.includes(key) && value !== undefined && value !== null) { - allTags.add(`${key}: ${value}`); - } - }); - }); - - return Array.from(allTags).sort(); - } - - public async getServerInfo(): Promise { - console.log(`[service] getServerInfo`); - const canCalculateFromCache = this.shouldUseServerCache() && reportCache.initialized && resultCache.initialized; - - if (!canCalculateFromCache) { - return await storage.getServerDataInfo(); - } - - const reports = reportCache.getAll(); - const results = resultCache.getAll(); - - const getTotalSizeBytes = (entity: T) => - entity.reduce((total, item) => total + item.sizeBytes, 0); - - const reportsFolderSize = getTotalSizeBytes(reports); - const resultsFolderSize = getTotalSizeBytes(results); - const dataFolderSize = reportsFolderSize + resultsFolderSize; - - return { - dataFolderSizeinMB: bytesToString(dataFolderSize), - numOfResults: results.length, - resultsFolderSizeinMB: bytesToString(resultsFolderSize), - numOfReports: reports.length, - reportsFolderSizeinMB: bytesToString(reportsFolderSize), - }; - } - - public async getConfig() { - const cached = this.shouldUseServerCache() && configCache.initialized ? configCache.config : undefined; - - if (cached) { - console.log(`[service] using cached config`); - - return cached; - } - - const { result, error } = await storage.readConfigFile(); - - if (error) console.error(`[service] getConfig | error: ${error.message}`); - - return { ...defaultConfig, ...(result ?? {}) }; - } - - public async updateConfig(config: Partial) { - console.log(`[service] updateConfig`, config); - const { result, error } = await storage.saveConfigFile(config); - - if (error) { - throw error; - } - - configCache.onChanged(result); - - return result; - } -} - -export const service = Service.getInstance(); diff --git a/app/lib/service/lifecycle.ts b/app/lib/service/lifecycle.ts deleted file mode 100644 index db2e3d4b..00000000 --- a/app/lib/service/lifecycle.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { configCache, reportCache, resultCache } from '@/app/lib/service/cache'; -import { cronService } from '@/app/lib/service/cron'; -import { env } from '@/app/config/env'; -import { isBuildStage } from '@/app/config/runtime'; - -const processKey = Symbol.for('playwright.reports.lifecycle'); - -export class Lifecycle { - private initialized = false; - private initPromise?: Promise; - - public static getInstance(): Lifecycle { - const nodeJsProcess = process as typeof process & { [key: symbol]: any }; - - if (!nodeJsProcess[processKey]) { - nodeJsProcess[processKey] = new Lifecycle(); - } - - return nodeJsProcess[processKey]; - } - - public async initialize(): Promise { - if (this.initialized) return; - - this.initPromise ??= this._performInitialization(); - - return this.initPromise; - } - - private async _performInitialization(): Promise { - console.log('[lifecycle] Starting application initialization'); - - try { - if (env.USE_SERVER_CACHE) { - await Promise.all([configCache.init(), reportCache.init(), resultCache.init()]); - console.log('[lifecycle] Caches initialized successfully'); - } - - if (!cronService.initialized && !isBuildStage) { - await cronService.init(); - console.log('[lifecycle] Cron service initialized successfully'); - } - - this.initialized = true; - console.log('[lifecycle] Application initialization complete'); - } catch (error) { - console.error('[lifecycle] Initialization failed:', error); - throw error; - } - } - - public isInitialized(): boolean { - return this.initialized; - } -} - -export const lifecycle = Lifecycle.getInstance(); diff --git a/app/lib/storage/index.ts b/app/lib/storage/index.ts deleted file mode 100644 index cf3abbe7..00000000 --- a/app/lib/storage/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './types'; -import { FS } from './fs'; -import { S3 } from './s3'; - -import { env } from '@/app/config/env'; - -export const storage = env.DATA_STORAGE === 's3' ? S3.getInstance() : FS; diff --git a/app/lib/storage/pagination.ts b/app/lib/storage/pagination.ts deleted file mode 100644 index 80f58e25..00000000 --- a/app/lib/storage/pagination.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface Pagination { - limit: number; - offset: number; -} - -export const handlePagination = (items: T[], pagination?: Pagination): T[] => { - if (!pagination) { - return items; - } - - return items.slice(pagination.offset, pagination.offset + pagination.limit); -}; - -export const parseFromRequest = (searchParams: URLSearchParams): Pagination => { - const limitQuery = searchParams.get('limit') ?? ''; - const offsetQuery = searchParams.get('offset') ?? ''; - - const limit = limitQuery ? parseInt(limitQuery, 10) : 20; - const offset = offsetQuery ? parseInt(offsetQuery, 10) : 0; - - return { limit, offset }; -}; diff --git a/app/lib/storage/s3.ts b/app/lib/storage/s3.ts deleted file mode 100644 index e69c76f3..00000000 --- a/app/lib/storage/s3.ts +++ /dev/null @@ -1,900 +0,0 @@ -import { randomUUID, type UUID } from 'crypto'; -import fs from 'fs/promises'; -import path from 'node:path'; -import { PassThrough, Readable } from 'node:stream'; - -import { type BucketItem, Client } from 'minio'; - -import { processBatch } from './batch'; -import { - Result, - Report, - ResultDetails, - ServerDataInfo, - isReportHistory, - ReadReportsInput, - ReadReportsOutput, - ReadResultsInput, - ReadResultsOutput, - ReportHistory, - ReportMetadata, - Storage, -} from './types'; -import { bytesToString } from './format'; -import { - REPORTS_FOLDER, - TMP_FOLDER, - REPORTS_BUCKET, - RESULTS_BUCKET, - REPORTS_PATH, - REPORT_METADATA_FILE, - APP_CONFIG_S3, - DATA_PATH, - DATA_FOLDER, -} from './constants'; -import { handlePagination } from './pagination'; -import { getFileReportID } from './file'; - -import { parse } from '@/app/lib/parser'; -import { serveReportRoute } from '@/app/lib/constants'; -import { generatePlaywrightReport } from '@/app/lib/pw'; -import { withError } from '@/app/lib/withError'; -import { env } from '@/app/config/env'; -import { SiteWhiteLabelConfig } from '@/app/types'; -import { defaultConfig, isConfigValid } from '@/app/lib/config'; - -const createClient = () => { - const endPoint = env.S3_ENDPOINT; - const accessKey = env.S3_ACCESS_KEY; - const secretKey = env.S3_SECRET_KEY; - const port = env.S3_PORT; - const region = env.S3_REGION; - - if (!endPoint) { - throw new Error('S3_ENDPOINT is required'); - } - - if (!accessKey) { - throw new Error('S3_ACCESS_KEY is required'); - } - - if (!secretKey) { - throw new Error('S3_SECRET_KEY is required'); - } - - console.log('[s3] creating client'); - - const client = new Client({ - endPoint, - accessKey, - secretKey, - region, - port, - useSSL: true, - }); - - return client; -}; - -export class S3 implements Storage { - private static instance: S3; - private readonly client: Client; - private readonly bucket: string; - private readonly batchSize: number; - - private constructor() { - this.client = createClient(); - this.bucket = env.S3_BUCKET; - this.batchSize = env.S3_BATCH_SIZE; - } - - public static getInstance() { - if (!S3.instance) { - S3.instance = new S3(); - } - - return S3.instance; - } - - private async ensureBucketExist() { - const { result: exist, error } = await withError(this.client.bucketExists(this.bucket)); - - if (exist && !error) { - return; - } - - if (error) { - console.error(error); - } - - const { error: bucketError } = await withError(this.client.makeBucket(this.bucket, env.S3_REGION)); - - if (bucketError) { - console.error(bucketError); - } - } - - private async write(dir: string, files: { name: string; content: Readable | Buffer | string; size?: number }[]) { - await this.ensureBucketExist(); - for (const file of files) { - const filePath = path.join(dir, file.name); - - console.log(`[s3] writing ${filePath}`); - - const content = typeof file.content === 'string' ? Buffer.from(file.content) : file.content; - - const contentSize = file.size ?? (Buffer.isBuffer(content) ? content.length : undefined); - - await this.client.putObject(this.bucket, path.normalize(filePath), content, contentSize); - } - } - - private async read(targetPath: string, contentType?: string | null) { - await this.ensureBucketExist(); - console.log(`[s3] read ${targetPath}`); - - const remotePath = targetPath.includes(REPORTS_BUCKET) ? targetPath : `${REPORTS_BUCKET}/${targetPath}`; - - console.log(`[s3] reading from remote path: ${remotePath}`); - - const { result: stream, error } = await withError(this.client.getObject(this.bucket, remotePath)); - - if (error ?? !stream) { - return { result: null, error }; - } - - const readStream = new Promise((resolve, reject) => { - const chunks: Uint8Array[] = []; - - stream.on('data', (chunk: Uint8Array) => { - chunks.push(chunk); - }); - - stream.on('end', () => { - const fullContent = Buffer.concat(chunks); - - resolve(fullContent); - }); - - stream.on('error', (error) => { - console.error(`[s3] failed to read stream: ${error.message}`); - reject(error); - }); - }); - - const { result, error: readError } = await withError(readStream); - - return { - result: contentType === 'text/html' ? result?.toString('utf-8') : result, - error: error ?? readError ?? null, - }; - } - - async clear(...path: string[]) { - console.log(`[s3] clearing ${path}`); - // avoid using "removeObjects" as it is not supported by every S3-compatible provider - // for example, Google Cloud Storage. - await processBatch(this, path, this.batchSize, async (object) => { - await this.client.removeObject(this.bucket, object); - }); - } - - async getFolderSize(folderPath: string): Promise<{ size: number; resultCount: number; indexCount: number }> { - let resultCount = 0; - let indexCount = 0; - let totalSize = 0; - const stream = this.client.listObjects(this.bucket, folderPath, true); - - return new Promise((resolve, reject) => { - stream.on('data', (obj) => { - if (obj.name?.endsWith('.zip')) { - resultCount += 1; - } - - if (obj.name?.endsWith('index.html') && !obj.name.includes('/trace/index.html')) { - indexCount += 1; - } - - totalSize += obj?.size ?? 0; - }); - - stream.on('error', (err) => { - reject(err); - }); - - stream.on('end', () => { - resolve({ size: totalSize, resultCount, indexCount }); - }); - }); - } - - async getServerDataInfo(): Promise { - await this.ensureBucketExist(); - console.log('[s3] getting server data'); - - const [results, reports] = await Promise.all([ - this.getFolderSize(RESULTS_BUCKET), - this.getFolderSize(REPORTS_BUCKET), - ]); - - const dataSize = results.size + reports.size; - - return { - dataFolderSizeinMB: bytesToString(dataSize), - numOfResults: results.resultCount, - resultsFolderSizeinMB: bytesToString(results.size), - numOfReports: reports.indexCount, - reportsFolderSizeinMB: bytesToString(reports.size), - }; - } - - async readFile(targetPath: string, contentType: string | null): Promise { - console.log(`[s3] reading ${targetPath} | ${contentType}`); - const { result, error } = await this.read(targetPath, contentType); - - if (error) { - console.error(`[s3] failed to read file ${targetPath}: ${error.message}`); - throw new Error(`[s3] failed to read file: ${error.message}`); - } - - return result!; - } - - async readResults(input?: ReadResultsInput): Promise { - await this.ensureBucketExist(); - - console.log('[s3] reading results'); - const listResultsStream = this.client.listObjectsV2(this.bucket, RESULTS_BUCKET, true); - - const files: BucketItem[] = []; - const resultSizes = new Map(); - - const findJsonFiles = new Promise((resolve, reject) => { - listResultsStream.on('data', async (file) => { - if (!file?.name) { - return; - } - - if (file.name.endsWith('.zip')) { - const resultID = path.basename(file.name, '.zip'); - - resultSizes.set(resultID, file.size); - } - - if (!file.name.endsWith('.json')) { - return; - } - - files.push(file); - }); - - listResultsStream.on('error', (err) => { - reject(err); - }); - - listResultsStream.on('end', () => { - resolve(files); - }); - }); - - const { result: jsonFiles } = await withError(findJsonFiles); - - console.log(`[s3] found ${(jsonFiles ?? [])?.length} json files`); - - if (!jsonFiles) { - return { - results: [], - total: 0, - }; - } - - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - - jsonFiles.sort((a, b) => getTimestamp(b.lastModified) - getTimestamp(a.lastModified)); - - // check if we can apply pagination early - const noFilters = !input?.project && !input?.pagination; - - const resultFiles = noFilters ? handlePagination(jsonFiles, input?.pagination) : jsonFiles; - - const results = await processBatch(this, resultFiles, this.batchSize, async (file) => { - console.log(`[s3.batch] reading result: ${JSON.stringify(file)}`); - const dataStream = await this.client.getObject(this.bucket, file.name!); - - let jsonString = ''; - - for await (const chunk of dataStream) { - jsonString += chunk.toString(); - } - - const parsed = JSON.parse(jsonString); - - return parsed; - }); - - let filteredResults = results.filter((file) => (input?.project ? file.project === input.project : file)); - - // Filter by tags if provided - if (input?.tags && input.tags.length > 0) { - const notMetadataKeys = ['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project']; - - filteredResults = filteredResults.filter((result) => { - const resultTags = Object.entries(result) - .filter(([key]) => !notMetadataKeys.includes(key)) - .map(([key, value]) => `${key}: ${value}`); - - return input.tags!.some((selectedTag) => resultTags.includes(selectedTag)); - }); - } - - // Filter by search if provided - if (input?.search?.trim()) { - const searchTerm = input.search.toLowerCase().trim(); - - filteredResults = filteredResults.filter((result) => { - // Search in title, resultID, project, and all metadata fields - const searchableFields = [ - result.title, - result.resultID, - result.project, - ...Object.entries(result) - .filter(([key]) => !['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project'].includes(key)) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const currentFiles = noFilters ? results : handlePagination(filteredResults, input?.pagination); - - return { - results: currentFiles.map((result) => { - const sizeBytes = resultSizes.get(result.resultID) ?? 0; - - return { - ...result, - sizeBytes, - size: result.size ?? bytesToString(sizeBytes), - }; - }) as Result[], - total: noFilters ? jsonFiles.length : filteredResults.length, - }; - } - - async readReports(input?: ReadReportsInput): Promise { - await this.ensureBucketExist(); - - console.log(`[s3] reading reports from external storage`); - const reportsStream = this.client.listObjectsV2(this.bucket, REPORTS_BUCKET, true); - - const reports: Report[] = []; - const reportSizes = new Map(); - - return new Promise((resolve, reject) => { - reportsStream.on('data', (file) => { - if (!file?.name) { - return; - } - - const reportID = getFileReportID(file.name); - - const newSize = (reportSizes.get(reportID) ?? 0) + file.size; - - reportSizes.set(reportID, newSize); - - if (!file.name.endsWith('index.html') || file.name.includes('trace')) { - return; - } - - const dir = path.dirname(file.name); - const id = path.basename(dir); - const parentDir = path.basename(path.dirname(dir)); - - const projectName = parentDir === REPORTS_PATH ? '' : parentDir; - - const noFilters = !input?.project && !input?.ids; - - const shouldFilterByProject = input?.project && projectName === input.project; - - const shouldFilterByID = input?.ids?.includes(id); - - const report = { - reportID: id, - project: projectName, - createdAt: file.lastModified, - reportUrl: `${serveReportRoute}/${projectName ? encodeURIComponent(projectName) : ''}/${id}/index.html`, - size: '', - sizeBytes: 0, - }; - - if (noFilters || shouldFilterByProject || shouldFilterByID) { - reports.push(report); - } - }); - - reportsStream.on('error', (err) => { - reject(err); - }); - - reportsStream.on('end', async () => { - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - - reports.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); - - const currentReports = handlePagination(reports, input?.pagination); - - const withMetadata = await this.getReportsMetadata(currentReports as ReportHistory[]); - - let filteredReports = withMetadata; - - // Filter by search if provided - if (input?.search && input.search.trim()) { - const searchTerm = input.search.toLowerCase().trim(); - - filteredReports = filteredReports.filter((report) => { - // Search in title, reportID, project, and all metadata fields - const searchableFields = [ - report.title, - report.reportID, - report.project, - ...Object.entries(report) - .filter( - ([key]) => - !['reportID', 'title', 'createdAt', 'size', 'sizeBytes', 'project', 'reportUrl', 'stats'].includes( - key, - ), - ) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const finalReports = handlePagination(filteredReports, input?.pagination); - - resolve({ - reports: finalReports.map((report) => { - const sizeBytes = reportSizes.get(report.reportID) ?? 0; - - return { - ...report, - sizeBytes, - size: bytesToString(sizeBytes), - }; - }), - total: filteredReports.length, - }); - }); - }); - } - - async getReportsMetadata(reports: ReportHistory[]): Promise { - return await processBatch(this, reports, this.batchSize, async (report) => { - console.log(`[s3.batch] reading report ${report.reportID} metadata`); - - const { result: metadata, error: metadataError } = await withError( - this.readOrParseReportMetadata(report.reportID, report.project), - ); - - if (metadataError) { - console.error(`[s3] failed to read or create metadata for ${report.reportID}: ${metadataError.message}`); - - return report; - } - - if (!metadata) { - return report; - } - - return Object.assign(metadata, report); - }); - } - - async readOrParseReportMetadata(id: string, projectName: string): Promise { - const { result: metadataContent, error: metadataError } = await withError( - this.readFile(path.join(REPORTS_BUCKET, projectName, id, REPORT_METADATA_FILE), 'utf-8'), - ); - - if (metadataError) console.error(`[s3] failed to read metadata for ${id}: ${metadataError.message}`); - - const metadata = metadataContent && !metadataError ? JSON.parse(metadataContent.toString()) : {}; - - if (isReportHistory(metadata)) { - console.log(`metadata found for report ${id}`); - - return metadata; - } - - console.log(`metadata file not found for ${id}, creating new metadata`); - try { - const { result: htmlContent, error: htmlError } = await withError( - this.readFile(path.join(REPORTS_BUCKET, projectName, id, 'index.html'), 'utf-8'), - ); - - if (htmlError) console.error(`[s3] failed to read index.html for ${id}: ${htmlError.message}`); - - const created = await this.parseReportMetadata( - id, - path.join(REPORTS_FOLDER, projectName, id), - { - project: projectName, - reportID: id, - }, - htmlContent?.toString(), - ); - - console.log(`metadata object created for ${id}: ${JSON.stringify(created)}`); - - await this.saveReportMetadata(id, path.join(REPORTS_FOLDER, projectName, id), created); - - Object.assign(metadata, created); - } catch (e) { - console.error(`failed to create metadata for ${id}: ${(e as Error).message}`); - } - - return metadata; - } - - async deleteResults(resultIDs: string[]): Promise { - const objects = resultIDs.flatMap((id) => [`${RESULTS_BUCKET}/${id}.json`, `${RESULTS_BUCKET}/${id}.zip`]); - - await withError(this.clear(...objects)); - } - - private async getReportObjects(reportsIDs: string[]): Promise { - const reportStream = this.client.listObjectsV2(this.bucket, REPORTS_BUCKET, true); - - const files: string[] = []; - - return new Promise((resolve, reject) => { - reportStream.on('data', (file) => { - if (!file?.name) { - return; - } - - const reportID = path.basename(path.dirname(file.name)); - - if (reportsIDs.includes(reportID)) { - files.push(file.name); - } - }); - - reportStream.on('error', (err) => { - reject(err); - }); - - reportStream.on('end', () => { - resolve(files); - }); - }); - } - - async deleteReports(reportIDs: string[]): Promise { - const objects = await this.getReportObjects(reportIDs); - - await withError(this.clear(...objects)); - } - - async generatePresignedUploadUrl(fileName: string) { - await this.ensureBucketExist(); - const objectKey = path.join(RESULTS_BUCKET, fileName); - const expiry = 30 * 60; // 30 minutes - - return this.client.presignedPutObject(this.bucket, objectKey, expiry); - } - - async saveResult(filename: string, stream: PassThrough) { - return await this.write(RESULTS_BUCKET, [ - { - name: filename, - content: stream, - }, - ]); - } - - async saveResultDetails(resultID: string, resultDetails: ResultDetails, size: number): Promise { - const metaData = { - resultID, - createdAt: new Date().toISOString(), - project: resultDetails?.project ?? '', - ...resultDetails, - size: bytesToString(size), - sizeBytes: size, - }; - - await this.write(RESULTS_BUCKET, [ - { - name: `${resultID}.json`, - content: JSON.stringify(metaData), - }, - ]); - - return metaData as Result; - } - - private async uploadReport(reportId: string, reportPath: string, remotePath: string) { - console.log(`[s3] upload report: ${reportPath}`); - - const files = await fs.readdir(reportPath, { recursive: true, withFileTypes: true }); - - await processBatch(this, files, this.batchSize, async (file) => { - if (!file.isFile()) { - return; - } - - console.log(`[s3] uploading file: ${JSON.stringify(file)}`); - - const nestedPath = (file as any).path.split(reportId).pop(); - const s3Path = path.join(remotePath, nestedPath ?? '', file.name); - - console.log(`[s3] uploading to ${s3Path}`); - - const { error } = await withError(this.uploadFileWithRetry(s3Path, path.join((file as any).path, file.name))); - - if (error) { - console.error(`[s3] failed to upload report: ${error.message}`); - throw new Error(`[s3] failed to upload report: ${error.message}`); - } - }); - } - - private async uploadFileWithRetry(remotePath: string, filePath: string, attempt = 1): Promise { - if (attempt > 3) { - throw new Error(`[s3] failed to upload file after ${attempt} attempts: ${filePath}`); - } - const { error } = await withError(this.client.fPutObject(this.bucket, remotePath, filePath, {})); - - if (error) { - console.error(`[s3] failed to upload file: ${error.message}`); - console.log(`[s3] will retry in 3s...`); - - return await this.uploadFileWithRetry(remotePath, filePath, attempt + 1); - } - } - - private async clearTempFolders(id?: string) { - const withReportPathMaybe = id ? ` for report ${id}` : ''; - - console.log(`[s3] clear temp folders${withReportPathMaybe}`); - - await withError(fs.rm(path.join(TMP_FOLDER, id ?? ''), { recursive: true, force: true })); - await withError(fs.rm(REPORTS_FOLDER, { recursive: true, force: true })); - } - - async generateReport(resultsIds: string[], metadata?: ReportMetadata): Promise { - console.log(`[s3] generate report from results: ${JSON.stringify(resultsIds)}`); - console.log(`[s3] create temp folders`); - const { error: mkdirReportsError } = await withError(fs.mkdir(REPORTS_FOLDER, { recursive: true })); - - if (mkdirReportsError) { - console.error(`[s3] failed to create reports folder: ${mkdirReportsError.message}`); - } - - const reportId = randomUUID(); - const tempFolder = path.join(TMP_FOLDER, reportId); - - const { error: mkdirTempError } = await withError(fs.mkdir(tempFolder, { recursive: true })); - - if (mkdirTempError) { - console.error(`[s3] failed to create temporary folder: ${mkdirTempError.message}`); - } - - const resultsStream = this.client.listObjects(this.bucket, RESULTS_BUCKET, true); - - console.log(`[s3] start processing...`); - for await (const result of resultsStream) { - const fileName = path.basename(result.name); - - const id = fileName.replace(path.extname(fileName), ''); - - if (resultsIds.includes(id)) { - console.log(`[s3] file id is in target results, downloading...`); - const localFilePath = path.join(tempFolder, fileName); - - const { error } = await withError(this.client.fGetObject(this.bucket, result.name, localFilePath)); - - if (error) { - console.error(`[s3] failed to download ${result.name}: ${error.message}`); - - throw new Error(`failed to download ${result.name}: ${error.message}`); - } - - console.log(`[s3] Downloaded: ${result.name} to ${localFilePath}`); - } - } - - const { reportPath } = await generatePlaywrightReport(reportId, metadata!); - - console.log(`[s3] report generated: ${reportId} | ${reportPath}`); - - const { result: info, error: parseReportMetadataError } = await withError( - this.parseReportMetadata(reportId, reportPath, metadata), - ); - - if (parseReportMetadataError) console.error(parseReportMetadataError.message); - - const remotePath = path.join(REPORTS_BUCKET, metadata?.project ?? '', reportId); - - const { error: uploadError } = await withError(this.uploadReport(reportId, reportPath, remotePath)); - - if (uploadError) { - console.error(`[s3] failed to upload report: ${uploadError.message}`); - } else { - const { error } = await withError(this.saveReportMetadata(reportId, reportPath, info ?? metadata ?? {})); - - if (error) console.error(`[s3] failed to save report metadata: ${error.message}`); - } - - await this.clearTempFolders(reportId); - - return reportId; - } - - private async saveReportMetadata(reportId: string, reportPath: string, metadata: ReportMetadata) { - console.log(`[s3] report uploaded: ${reportId}, uploading metadata to ${reportPath}`); - const { error: metadataError } = await withError( - this.write(path.join(REPORTS_BUCKET, metadata.project ?? '', reportId), [ - { - name: REPORT_METADATA_FILE, - content: JSON.stringify(metadata), - }, - ]), - ); - - if (metadataError) console.error(`[s3] failed to upload report metadata: ${metadataError.message}`); - } - - private async parseReportMetadata( - reportId: string, - reportPath: string, - metadata?: Record, - htmlContent?: string, // to pass file content if stored on s3 - ): Promise { - console.log(`[s3] creating report metadata for ${reportId} and ${reportPath}`); - const html = htmlContent ?? (await fs.readFile(path.join(reportPath, 'index.html'), 'utf-8')); - - const info = await parse(html as string); - - const content = Object.assign(info, metadata, { - reportId, - createdAt: new Date().toISOString(), - }); - - return content; - } - - async readConfigFile(): Promise<{ result?: SiteWhiteLabelConfig; error: Error | null }> { - await this.ensureBucketExist(); - console.log(`[s3] checking config file`); - - const { result: stream, error } = await withError(this.client.getObject(this.bucket, APP_CONFIG_S3)); - - if (error) { - console.error(`[s3] failed to read config file: ${error.message}`); - - return { error }; - } - - let existingConfig = ''; - - for await (const chunk of stream ?? []) { - existingConfig += chunk.toString(); - } - - try { - const parsed = JSON.parse(existingConfig); - - const isValid = isConfigValid(parsed); - - if (!isValid) { - return { error: new Error('invalid config') }; - } - - // ensure custom images available locally in data folder - for (const image of [ - { path: parsed.faviconPath, default: defaultConfig.faviconPath }, - { path: parsed.logoPath, default: defaultConfig.logoPath }, - ]) { - if (!image) continue; - if (image.path === image.default) continue; - - const localPath = path.join(DATA_FOLDER, image.path); - const { error: accessError } = await withError(fs.access(localPath)); - - if (accessError) { - const remotePath = path.join(DATA_PATH, image.path); - - console.log(`[s3] downloading config image: ${remotePath} to ${localPath}`); - await this.client.fGetObject(this.bucket, remotePath, localPath); - } - } - - return { result: parsed, error: null }; - } catch (e) { - return { error: new Error(`failed to parse config: ${e instanceof Error ? e.message : e}`) }; - } - } - - async saveConfigFile(config: Partial) { - console.log(`[s3] writing config file`); - - const { result: existingConfig, error: readExistingConfigError } = await this.readConfigFile(); - - if (readExistingConfigError) { - console.error(`[s3] failed to read existing config file: ${readExistingConfigError.message}`); - } - - const { error: clearExistingConfigError } = await withError(this.clear(APP_CONFIG_S3)); - - if (clearExistingConfigError) { - console.error(`[s3] failed to clear existing config file: ${clearExistingConfigError.message}`); - } - - const uploadConfig = { ...(existingConfig ?? {}), ...config } as SiteWhiteLabelConfig; - - const isDefaultImage = (key: keyof SiteWhiteLabelConfig) => config[key] && config[key] === defaultConfig[key]; - - const shouldBeUploaded = async (key: keyof SiteWhiteLabelConfig) => { - if (!config[key]) return false; - if (isDefaultImage(key)) return false; - - const { result } = await withError(this.client.statObject(this.bucket, uploadConfig.logoPath)); - - if (!result) { - return true; - } - - return false; - }; - - if (await shouldBeUploaded('logoPath')) { - await this.uploadConfigImage(uploadConfig.logoPath); - } - - if (await shouldBeUploaded('faviconPath')) { - await this.uploadConfigImage(uploadConfig.faviconPath); - } - - const { error } = await withError( - this.write(DATA_PATH, [ - { - name: 'config.json', - content: JSON.stringify(uploadConfig, null, 2), - }, - ]), - ); - - if (error) console.error(`[s3] failed to write config file: ${error.message}`); - - return { result: uploadConfig, error }; - } - - private async uploadConfigImage(imagePath: string): Promise { - console.log(`[s3] uploading config image: ${imagePath}`); - - const localPath = path.join(DATA_FOLDER, imagePath); - const remotePath = path.join(DATA_PATH, imagePath); - - const { error } = await withError(this.uploadFileWithRetry(remotePath, localPath)); - - if (error) { - console.error(`[s3] failed to upload config image: ${error.message}`); - - return error; - } - - return null; - } -} diff --git a/app/lib/tailwind.ts b/app/lib/tailwind.ts deleted file mode 100644 index 72595ae7..00000000 --- a/app/lib/tailwind.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -import { ReportTestOutcome } from './parser'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - -enum OutcomeColors { - Success = 'success', - Primary = 'primary', - Danger = 'danger', - Warning = 'warning', - Default = 'default', -} - -export function testStatusToColor(outcome?: ReportTestOutcome): { - title: string; - colorName: OutcomeColors; - color: string; -} { - const outcomes = { - [ReportTestOutcome.Expected]: { - title: 'Passed', - colorName: OutcomeColors.Success, - color: 'text-success-500', - }, - [ReportTestOutcome.Unexpected]: { - title: 'Failed', - colorName: OutcomeColors.Danger, - color: 'text-danger-500', - }, - [ReportTestOutcome.Flaky]: { - title: 'Flaky', - colorName: OutcomeColors.Warning, - color: 'text-warning-500', - }, - [ReportTestOutcome.Skipped]: { - title: 'Skipped', - colorName: OutcomeColors.Default, - color: 'text-gray-500', - }, - unknown: { - title: 'N/A', - colorName: OutcomeColors.Primary, - color: 'text-gray-200', - }, - }; - - return outcome ? outcomes[outcome] : outcomes.unknown; -} diff --git a/app/login/page.tsx b/app/login/page.tsx deleted file mode 100644 index 97f0312b..00000000 --- a/app/login/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Suspense } from 'react'; - -import LoginForm from '@/app/components/login-form'; - -export default async function LoginPage() { - // useSearchParams() should be wrapped in a suspense boundary. - // Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout - return ( - - - - ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index c3b483b3..00000000 --- a/app/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function HomePage() { - redirect('/reports'); -} diff --git a/app/settings/types.ts b/app/settings/types.ts deleted file mode 100644 index d95082f6..00000000 --- a/app/settings/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface ServerConfig { - title?: string; - headerLinks?: Record; - logoPath?: string; - faviconPath?: string; - reporterPaths?: string[]; - cron?: { - resultExpireDays?: number; - resultExpireCronSchedule?: string; - reportExpireDays?: number; - reportExpireCronSchedule?: string; - }; - jira?: { - baseUrl?: string; - email?: string; - apiToken?: string; - projectKey?: string; - }; -} - -export interface JiraConfig { - configured: boolean; - baseUrl?: string; - defaultProjectKey?: string; - issueTypes?: Array<{ - id: string; - name: string; - description: string; - }>; - error?: string; -} diff --git a/app/styles/globals.css b/app/styles/globals.css deleted file mode 100644 index f42870f0..00000000 --- a/app/styles/globals.css +++ /dev/null @@ -1,21 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --chart-1: 160 60% 45%; - --chart-2: 350 70% 56%; - --chart-3: 0 8% 65%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } - - .dark { - --chart-1: 160 60% 45%; - --chart-2: 350 70% 56%; - --chart-3: 0 8% 65%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } - } \ No newline at end of file diff --git a/app/types/index.ts b/app/types/index.ts deleted file mode 100644 index 4e9c2265..00000000 --- a/app/types/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { SVGProps } from 'react'; - -import { type HeaderLinks } from '@/app/config/site'; - -export type IconSvgProps = SVGProps & { - size?: number; -}; - -export type UUID = `${string}-${string}-${string}-${string}-${string}`; - -export interface JiraConfig { - baseUrl?: string; - email?: string; - apiToken?: string; - projectKey?: string; -} - -export interface SiteWhiteLabelConfig { - title: string; - headerLinks: HeaderLinks; - logoPath: string; - faviconPath: string; - reporterPaths?: string[]; - authRequired?: boolean; - serverCache?: boolean; - dataStorage?: string; - s3Endpoint?: string; - s3Bucket?: string; - cron?: { - resultExpireDays?: number; - resultExpireCronSchedule?: string; - reportExpireDays?: number; - reportExpireCronSchedule?: string; - }; - jira?: JiraConfig; -} - -export interface EnvInfo { - authRequired: boolean; - serverCache: boolean | undefined; - dataStorage: string | undefined; - s3Endpoint: string | undefined; - s3Bucket: string | undefined; -} diff --git a/apps/backend/.env.example b/apps/backend/.env.example new file mode 100644 index 00000000..d3a50aa2 --- /dev/null +++ b/apps/backend/.env.example @@ -0,0 +1,24 @@ +# Server configuration +PORT=3001 +HOST=0.0.0.0 + +# Auth configuration +AUTH_SECRET= +AUTH_URL=http://localhost:3000 + +# API token details +API_TOKEN='my-api-token' +UI_AUTH_EXPIRE_HOURS='2' + +# Storage details +DATA_STORAGE=fs # could be s3 + +# S3 related configuration if DATA_STORAGE is "s3" +S3_ENDPOINT="s3.endpoint" +S3_ACCESS_KEY="some_access_key" +S3_SECRET_KEY="some_secret_key" +S3_PORT=9000 # optional +S3_REGION="us-east-1" +S3_BUCKET="bucket_name" # by default "playwright-reports-server" +S3_BATCH_SIZE=10 # by default 10 +S3_MULTIPART_CHUNK_SIZE_MB=25 # by default 25MB, controls chunk size for multipart uploads to reduce RAM usage \ No newline at end of file diff --git a/apps/backend/package-lock.json b/apps/backend/package-lock.json new file mode 100644 index 00000000..1f76d0c3 --- /dev/null +++ b/apps/backend/package-lock.json @@ -0,0 +1,6452 @@ +{ + "name": "playwright-reports-server-backend", + "version": "5.7.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playwright-reports-server-backend", + "version": "5.7.4", + "dependencies": { + "@aws-sdk/client-s3": "^3.932.0", + "@aws-sdk/s3-request-presigner": "^3.932.0", + "@fastify/cookie": "^10.0.1", + "@fastify/cors": "^10.0.1", + "@fastify/jwt": "^9.0.1", + "@fastify/multipart": "^9.0.1", + "@fastify/static": "^8.0.2", + "@playwright/test": "latest", + "better-sqlite3": "^12.4.1", + "busboy": "^1.6.0", + "croner": "^9.0.0", + "dotenv": "^17.2.3", + "envalid": "^8.0.0", + "fastify": "^5.2.0", + "get-folder-size": "5.0.0", + "jsonwebtoken": "^9.0.2", + "jszip": "^3.10.1", + "mime": "^4.0.4", + "sharp": "^0.33.5", + "uuid": "^10.0.0", + "zod": "^4.1.13" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/busboy": "^1.5.4", + "@types/jsonwebtoken": "^9.0.7", + "@types/node": ">=22.10.2", + "@types/uuid": "^10.0.0", + "concurrently": "^9.2.1", + "tsx": "^4.21.0", + "typescript": "5.2.2", + "vite": "^6.0.3" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.943.0.tgz", + "integrity": "sha512-UOX8/1mmNaRmEkxoIVP2+gxd5joPJqz+fygRqlIXON1cETLGoctinMwQs7qU8g8hghm76TU2G6ZV6sLH8cySMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.943.0", + "@aws-sdk/credential-provider-node": "3.943.0", + "@aws-sdk/middleware-bucket-endpoint": "3.936.0", + "@aws-sdk/middleware-expect-continue": "3.936.0", + "@aws-sdk/middleware-flexible-checksums": "3.943.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-location-constraint": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-sdk-s3": "3.943.0", + "@aws-sdk/middleware-ssec": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.943.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/signature-v4-multi-region": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.943.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/eventstream-serde-browser": "^4.2.5", + "@smithy/eventstream-serde-config-resolver": "^4.3.5", + "@smithy/eventstream-serde-node": "^4.2.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-blob-browser": "^4.2.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/hash-stream-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/md5-js": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.943.0.tgz", + "integrity": "sha512-kOTO2B8Ks2qX73CyKY8PAajtf5n39aMe2spoiOF5EkgSzGV7hZ/HONRDyADlyxwfsX39Q2F2SpPUaXzon32IGw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.943.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.943.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.943.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.943.0.tgz", + "integrity": "sha512-8CBy2hI9ABF7RBVQuY1bgf/ue+WPmM/hl0adrXFlhnhkaQP0tFY5zhiy1Y+n7V+5f3/ORoHBmCCQmcHDDYJqJQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.943.0.tgz", + "integrity": "sha512-WnS5w9fK9CTuoZRVSIHLOMcI63oODg9qd1vXMYb7QGLGlfwUm4aG3hdu7i9XvYrpkQfE3dzwWLtXF4ZBuL1Tew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.943.0.tgz", + "integrity": "sha512-SA8bUcYDEACdhnhLpZNnWusBpdmj4Vl67Vxp3Zke7SvoWSYbuxa+tiDiC+c92Z4Yq6xNOuLPW912ZPb9/NsSkA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.943.0.tgz", + "integrity": "sha512-BcLDb8l4oVW+NkuqXMlO7TnM6lBOWW318ylf4FRED/ply5eaGxkQYqdGvHSqGSN5Rb3vr5Ek0xpzSjeYD7C8Kw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/credential-provider-env": "3.943.0", + "@aws-sdk/credential-provider-http": "3.943.0", + "@aws-sdk/credential-provider-login": "3.943.0", + "@aws-sdk/credential-provider-process": "3.943.0", + "@aws-sdk/credential-provider-sso": "3.943.0", + "@aws-sdk/credential-provider-web-identity": "3.943.0", + "@aws-sdk/nested-clients": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.943.0.tgz", + "integrity": "sha512-9iCOVkiRW+evxiJE94RqosCwRrzptAVPhRhGWv4osfYDhjNAvUMyrnZl3T1bjqCoKNcETRKEZIU3dqYHnUkcwQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/nested-clients": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.943.0.tgz", + "integrity": "sha512-14eddaH/gjCWoLSAELVrFOQNyswUYwWphIt+PdsJ/FqVfP4ay2HsiZVEIYbQtmrKHaoLJhiZKwBQRjcqJDZG0w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.943.0", + "@aws-sdk/credential-provider-http": "3.943.0", + "@aws-sdk/credential-provider-ini": "3.943.0", + "@aws-sdk/credential-provider-process": "3.943.0", + "@aws-sdk/credential-provider-sso": "3.943.0", + "@aws-sdk/credential-provider-web-identity": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.943.0.tgz", + "integrity": "sha512-GIY/vUkthL33AdjOJ8r9vOosKf/3X+X7LIiACzGxvZZrtoOiRq0LADppdiKIB48vTL63VvW+eRIOFAxE6UDekw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.943.0.tgz", + "integrity": "sha512-1c5G11syUrru3D9OO6Uk+ul5e2lX1adb+7zQNyluNaLPXP6Dina6Sy6DFGRLu7tM8+M7luYmbS3w63rpYpaL+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.943.0", + "@aws-sdk/core": "3.943.0", + "@aws-sdk/token-providers": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.943.0.tgz", + "integrity": "sha512-VtyGKHxICSb4kKGuaqotxso8JVM8RjCS3UYdIMOxUt9TaFE/CZIfZKtjTr+IJ7M0P7t36wuSUb/jRLyNmGzUUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/nested-clients": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz", + "integrity": "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz", + "integrity": "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.943.0.tgz", + "integrity": "sha512-J2oYbAQXTFEezs5m2Vij6H3w71K1hZfCtb85AsR/2Ovp/FjABMnK+Es1g1edRx6KuMTc9HkL/iGU4e+ek+qCZw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz", + "integrity": "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.943.0.tgz", + "integrity": "sha512-kd2mALfthU+RS9NsPS+qvznFcPnVgVx9mgmStWCPn5Qc5BTnx4UAtm+HPA+XZs+zxOopp+zmAfE4qxDHRVONBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz", + "integrity": "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.943.0.tgz", + "integrity": "sha512-956n4kVEwFNXndXfhSAN5wO+KRgqiWEEY+ECwLvxmmO8uQ0NWOa8l6l65nTtyuiWzMX81c9BvlyNR5EgUeeUvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.943.0.tgz", + "integrity": "sha512-anFtB0p2FPuyUnbOULwGmKYqYKSq1M73c9uZ08jR/NCq6Trjq9cuF5TFTeHwjJyPRb4wMf2Qk859oiVfFqnQiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.943.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.943.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.943.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.943.0.tgz", + "integrity": "sha512-r79MlUb7jeydV0caSz/emoyttzDdxgSkS/8ZU3lawkoTTnWCt+1nB4VA2xzOAPzP2dSdwNVpuAdY7uD1t2f0wA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/signature-v4-multi-region": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-format-url": "3.936.0", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.943.0.tgz", + "integrity": "sha512-KKvmxNQ/FZbM6ml6nKd8ltDulsUojsXnMJNgf1VHTcJEbADC/6mVWOq0+e9D0WP1qixUBEuMjlS2HqD5KoqwEg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.943.0.tgz", + "integrity": "sha512-cRKyIzwfkS+XztXIFPoWORuaxlIswP+a83BJzelX4S1gUZ7FcXB4+lj9Jxjn8SbQhR4TPU3Owbpu+S7pd6IRbQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.943.0", + "@aws-sdk/nested-clients": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.936.0.tgz", + "integrity": "sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.943.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.943.0.tgz", + "integrity": "sha512-gn+ILprVRrgAgTIBk2TDsJLRClzIOdStQFeFTcN0qpL8Z4GBCqMFhw7O7X+MM55Stt5s4jAauQ/VvoqmCADnQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.943.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", + "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "license": "MIT" + }, + "node_modules/@fastify/cookie": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-10.0.1.tgz", + "integrity": "sha512-NV/wbCUv4ETJ5KM1KMu0fLx0nSCm9idIxwg66NZnNbfPQH3rdbx6k0qRs5uy0y+MhBgvDudYRA30KlK659chyw==", + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.1", + "fastify-plugin": "^5.0.0" + } + }, + "node_modules/@fastify/cors": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-10.1.0.tgz", + "integrity": "sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "mnemonist": "0.40.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.1.0.tgz", + "integrity": "sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/jwt": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-9.1.0.tgz", + "integrity": "sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "@lukeed/ms": "^2.0.2", + "fast-jwt": "^5.0.0", + "fastify-plugin": "^5.0.0", + "steed": "^1.1.3" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/multipart": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.3.0.tgz", + "integrity": "sha512-NpeKipTOjjL1dA7SSlRMrOWWtrE8/0yKOmeudkdQoEaz4sVDJw5MVdZIahsWhvpc3YTN7f04f9ep/Y65RKoOWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@fastify/deepmerge": "^3.0.0", + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0", + "secure-json-parse": "^4.0.0" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz", + "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^0.5.4", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^11.0.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "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/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.18.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.6.tgz", + "integrity": "sha512-8Q/ugWqfDUEU1Exw71+DoOzlONJ2Cn9QA8VeeDzLLjzO/qruh9UKFzbszy4jXcIYgGofxYiT0t1TT6+CT/GupQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.5.tgz", + "integrity": "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.5.tgz", + "integrity": "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.5.tgz", + "integrity": "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.5.tgz", + "integrity": "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.5.tgz", + "integrity": "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.6.tgz", + "integrity": "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.5.tgz", + "integrity": "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz", + "integrity": "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.13.tgz", + "integrity": "sha512-X4za1qCdyx1hEVVXuAWlZuK6wzLDv1uw1OY9VtaYy1lULl661+frY7FeuHdYdl7qAARUxH2yvNExU2/SmRFfcg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.18.6", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.13.tgz", + "integrity": "sha512-RzIDF9OrSviXX7MQeKOm8r/372KTyY8Jmp6HNKOOYlrguHADuM3ED/f4aCyNhZZFLG55lv5beBin7nL0Nzy1Dw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.9", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.9.tgz", + "integrity": "sha512-SUnZJMMo5yCmgjopJbiNeo1vlr8KvdnEfIHV9rlD77QuOGdRotIVBcOrBuMr+sI9zrnhtDtLP054bZVbpZpiQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.18.6", + "@smithy/middleware-endpoint": "^4.3.13", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.12.tgz", + "integrity": "sha512-TKc6FnOxFULKxLgTNHYjcFqdOYzXVPFFVm5JhI30F3RdhT7nYOtOsjgaOwfDRmA/3U66O9KaBQ3UHoXwayRhAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.9", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.15.tgz", + "integrity": "sha512-94NqfQVo+vGc5gsQ9SROZqOvBkGNMQu6pjXbnn8aQvBUhc31kx49gxlkBEqgmaZQHUUfdRUin5gK/HlHKmbAwg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.9", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", + "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", + "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz", + "integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/croner": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/croner/-/croner-9.1.0.tgz", + "integrity": "sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==", + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/envalid": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.1.tgz", + "integrity": "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.1.1.tgz", + "integrity": "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-jwt": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-5.0.6.tgz", + "integrity": "sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==", + "license": "Apache-2.0", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "asn1.js": "^5.4.1", + "ecdsa-sig-formatter": "^1.0.11", + "mnemonist": "^0.40.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastfall": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", + "integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==", + "license": "MIT", + "dependencies": { + "reusify": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fastify": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.2.tgz", + "integrity": "sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastparallel": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", + "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4", + "xtend": "^4.0.2" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fastseries": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-1.7.2.tgz", + "integrity": "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.0", + "xtend": "^4.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/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-folder-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-5.0.0.tgz", + "integrity": "sha512-+fgtvbL83tSDypEK+T411GDBQVQtxv+qtQgbV+HVa/TYubqDhNd5ghH/D6cOHY9iC5/88GtOZB7WI8PXy2A3bg==", + "license": "MIT", + "bin": { + "get-folder-size": "bin/get-folder-size.js" + }, + "engines": { + "node": ">=18.11.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mnemonist": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.40.0.tgz", + "integrity": "sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.4" + } + }, + "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==", + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/pino": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", + "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "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.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/steed": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/steed/-/steed-1.1.3.tgz", + "integrity": "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==", + "license": "MIT", + "dependencies": { + "fastfall": "^1.5.0", + "fastparallel": "^2.2.0", + "fastq": "^1.3.0", + "fastseries": "^1.7.0", + "reusify": "^1.0.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "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/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, + "node_modules/tsx/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/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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_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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/apps/backend/package.json b/apps/backend/package.json new file mode 100644 index 00000000..a86d0d78 --- /dev/null +++ b/apps/backend/package.json @@ -0,0 +1,54 @@ +{ + "name": "@playwright-reports/backend", + "version": "5.7.4", + "description": "Playwright Reports Server Backend", + "type": "module", + "scripts": { + "dev": "tsx watch --clear-screen=false src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit", + "test": "npx playwright test ../../tests/api/", + "lint": "biome check src/", + "lint:fix": "biome check src/ --write", + "format": "biome format src/ --write", + "format:check": "biome format src/", + "clean": "rm -rf dist" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.932.0", + "@aws-sdk/s3-request-presigner": "^3.932.0", + "@fastify/cookie": "^10.0.1", + "@fastify/cors": "^10.0.1", + "@fastify/jwt": "^9.0.1", + "@fastify/multipart": "^9.0.1", + "@fastify/static": "^8.0.2", + "@playwright-reports/shared": "*", + "@playwright/test": "latest", + "better-sqlite3": "^12.4.1", + "busboy": "^1.6.0", + "croner": "^9.0.0", + "dotenv": "^17.2.3", + "envalid": "^8.0.0", + "fastify": "^5.2.0", + "get-folder-size": "5.0.0", + "jsonwebtoken": "^9.0.2", + "jszip": "^3.10.1", + "mime": "^4.0.4", + "sharp": "^0.33.5", + "uuid": "^10.0.0", + "zod": "^4.1.13" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/busboy": "^1.5.4", + "@types/jsonwebtoken": "^9.0.7", + "@types/node": ">=22.10.2", + "@types/uuid": "^10.0.0", + "tsx": "^4.21.0", + "typescript": "5.2.2" + }, + "engines": { + "node": ">=22.0.0" + } +} diff --git a/public/favicon.ico b/apps/backend/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to apps/backend/public/favicon.ico diff --git a/public/logo.svg b/apps/backend/public/logo.svg similarity index 100% rename from public/logo.svg rename to apps/backend/public/logo.svg diff --git a/app/config/env.ts b/apps/backend/src/config/env.ts similarity index 51% rename from app/config/env.ts rename to apps/backend/src/config/env.ts index 922dd3c4..94dce098 100644 --- a/app/config/env.ts +++ b/apps/backend/src/config/env.ts @@ -1,11 +1,13 @@ -import { cleanEnv, str, num, bool } from 'envalid'; +import { cleanEnv, num, str } from 'envalid'; export const env = cleanEnv(process.env, { API_TOKEN: str({ desc: 'API token for authorization', default: undefined }), - UI_AUTH_EXPIRE_HOURS: str({ desc: 'How much hours are allowed to keep auth session valid', default: '2' }), + UI_AUTH_EXPIRE_HOURS: str({ + desc: 'How much hours are allowed to keep auth session valid', + default: '2', + }), AUTH_SECRET: str({ desc: 'Secret for JWT', default: undefined }), DATA_STORAGE: str({ desc: 'Where to store data', default: 'fs' }), - USE_SERVER_CACHE: bool({ desc: 'Use server side indexed cache for backend queries', default: false }), S3_ENDPOINT: str({ desc: 'S3 endpoint', default: undefined }), S3_ACCESS_KEY: str({ desc: 'S3 access key', default: undefined }), S3_SECRET_KEY: str({ desc: 'S3 secret key', default: undefined }), @@ -13,14 +15,35 @@ export const env = cleanEnv(process.env, { S3_REGION: str({ desc: 'S3 region', default: undefined }), S3_BUCKET: str({ desc: 'S3 bucket', default: 'playwright-reports-server' }), S3_BATCH_SIZE: num({ desc: 'S3 batch size', default: 10 }), - RESULT_EXPIRE_DAYS: num({ desc: 'How much days to keep results', default: undefined }), - RESULT_EXPIRE_CRON_SCHEDULE: str({ desc: 'Cron schedule for results cleanup', default: '33 3 * * *' }), - REPORT_EXPIRE_DAYS: num({ desc: 'How much days to keep reports', default: undefined }), - REPORT_EXPIRE_CRON_SCHEDULE: str({ desc: 'Cron schedule for reports cleanup', default: '44 4 * * *' }), - // Jira API Configuration - JIRA_BASE_URL: str({ desc: 'Jira base URL (e.g., https://your-domain.atlassian.net)', default: undefined }), + S3_MULTIPART_CHUNK_SIZE_MB: num({ + desc: 'S3 multipart upload chunk size in MB', + default: 25, + }), + RESULT_EXPIRE_DAYS: num({ + desc: 'How much days to keep results', + default: undefined, + }), + RESULT_EXPIRE_CRON_SCHEDULE: str({ + desc: 'Cron schedule for results cleanup', + default: '33 3 * * *', + }), + REPORT_EXPIRE_DAYS: num({ + desc: 'How much days to keep reports', + default: undefined, + }), + REPORT_EXPIRE_CRON_SCHEDULE: str({ + desc: 'Cron schedule for reports cleanup', + default: '44 4 * * *', + }), + JIRA_BASE_URL: str({ + desc: 'Jira base URL (e.g., https://your-domain.atlassian.net)', + default: undefined, + }), JIRA_EMAIL: str({ desc: 'Jira user email', default: undefined }), JIRA_API_TOKEN: str({ desc: 'Jira API token', default: undefined }), - JIRA_PROJECT_KEY: str({ desc: 'Default Jira project key (optional)', default: undefined }), + JIRA_PROJECT_KEY: str({ + desc: 'Default Jira project key (optional)', + default: undefined, + }), API_BASE_PATH: str({ desc: 'Base path for the API', default: '' }), }); diff --git a/app/config/site.ts b/apps/backend/src/config/site.ts similarity index 100% rename from app/config/site.ts rename to apps/backend/src/config/site.ts diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts new file mode 100644 index 00000000..889a4e8b --- /dev/null +++ b/apps/backend/src/index.ts @@ -0,0 +1,108 @@ +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import fastifyCookie from '@fastify/cookie'; +import fastifyCors from '@fastify/cors'; +import fastifyJwt from '@fastify/jwt'; +import fastifyMultipart from '@fastify/multipart'; +import fastifyStatic from '@fastify/static'; +import { config } from 'dotenv'; +import Fastify from 'fastify'; +import { lifecycle } from './lib/service/lifecycle.js'; +import { registerApiRoutes } from './routes/index.js'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +config({ path: join(__dirname, '../../../.env') }); + +const PORT = Number.parseInt(process.env.PORT || '3001', 10); +const HOST = process.env.HOST || '0.0.0.0'; +const AUTH_SECRET = process.env.AUTH_SECRET || 'development-secret-change-in-production'; + +async function start() { + const fastify = Fastify({ + logger: { + level: process.env.LOG_LEVEL || 'info', + }, + }); + + await fastify.register(fastifyCors, { + origin: process.env.CORS_ORIGIN || true, + credentials: true, + }); + + await fastify.register(fastifyCookie); + + await fastify.register(fastifyJwt, { + secret: AUTH_SECRET, + cookie: { + cookieName: 'token', + signed: false, + }, + }); + + await fastify.register(fastifyMultipart); + + fastify.get('/api/ping', async () => { + return { + status: 'ok', + timestamp: new Date().toISOString(), + }; + }); + + fastify.get('/api/health', async () => { + return { + status: 'healthy', + timestamp: new Date().toISOString(), + }; + }); + + await registerApiRoutes(fastify); + + const dataDir = process.env.DATA_DIR || join(process.cwd(), 'data'); + await fastify.register(fastifyStatic, { + root: dataDir, + prefix: '/data/', + decorateReply: false, + }); + + if (process.env.NODE_ENV === 'production') { + const frontendDistPath = + process.env.FRONTEND_DIST || join(process.cwd(), '..', '..', 'apps', 'frontend', 'dist'); + + await fastify.register(fastifyStatic, { + root: frontendDistPath, + decorateReply: true, + }); + + // spa fallback for non-api routes + fastify.setNotFoundHandler(async (request, reply) => { + if (!request.url.startsWith('/api') && !request.url.startsWith('/data')) { + return reply.sendFile('index.html'); + } + return reply.code(404).send({ error: 'Not Found' }); + }); + } + + console.log('[server] Initializing databases and services...'); + await lifecycle.initialize(); + console.log('[server] Initialization complete'); + + const closeGracefully = async (signal: string) => { + fastify.log.info(`Received signal to terminate: ${signal}`); + await lifecycle.cleanup(); + await fastify.close(); + process.exit(0); + }; + + process.on('SIGINT', () => closeGracefully('SIGINT')); + process.on('SIGTERM', () => closeGracefully('SIGTERM')); + + try { + await fastify.listen({ port: PORT, host: HOST }); + fastify.log.info(`Server listening on http://${HOST}:${PORT}`); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +} + +await start(); diff --git a/app/lib/auth.ts b/apps/backend/src/lib/auth.ts similarity index 100% rename from app/lib/auth.ts rename to apps/backend/src/lib/auth.ts diff --git a/app/lib/config.ts b/apps/backend/src/lib/config.ts similarity index 89% rename from app/lib/config.ts rename to apps/backend/src/lib/config.ts index 6fb51709..f2b6e59b 100644 --- a/app/lib/config.ts +++ b/apps/backend/src/lib/config.ts @@ -1,5 +1,5 @@ -import { SiteWhiteLabelConfig } from '@/app/types'; -import { defaultLinks } from '@/app/config/site'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import { defaultLinks } from '../config/site.js'; export const defaultConfig: SiteWhiteLabelConfig = { title: 'Cyborg Tests', diff --git a/app/lib/constants.ts b/apps/backend/src/lib/constants.ts similarity index 100% rename from app/lib/constants.ts rename to apps/backend/src/lib/constants.ts diff --git a/app/lib/network.ts b/apps/backend/src/lib/network.ts similarity index 60% rename from app/lib/network.ts rename to apps/backend/src/lib/network.ts index 29e90f9b..6e852805 100644 --- a/app/lib/network.ts +++ b/apps/backend/src/lib/network.ts @@ -1,10 +1,8 @@ -import { defaultProjectName } from './constants'; +import { defaultProjectName } from './constants.js'; -export class CommonResponseFactory { - static buildUnauthorizedResponse(): Response { - return new Response('Unauthorized', { status: 401 }); - } -} +export const buildUnauthorizedResponse = (): Response => { + return new Response('Unauthorized', { status: 401 }); +}; export const withQueryParams = (url: string, params: Record): string => { if (params?.project === defaultProjectName) { diff --git a/app/lib/parser/index.ts b/apps/backend/src/lib/parser/index.ts similarity index 88% rename from app/lib/parser/index.ts rename to apps/backend/src/lib/parser/index.ts index b4f1e491..3b34c816 100644 --- a/app/lib/parser/index.ts +++ b/apps/backend/src/lib/parser/index.ts @@ -1,10 +1,8 @@ import JSZip from 'jszip'; +import { withError } from '../../lib/withError.js'; +import type { ReportInfo } from './types.js'; -import { type ReportInfo } from './types'; - -import { withError } from '@/app/lib/withError'; - -export * from './types'; +export * from './types.js'; /** * @@ -42,7 +40,7 @@ export const parse = async (html: string): Promise => { const reportFile = zip.file('report.json'); if (!reportFile) { - throw Error('[report parser] no report.json file found in the zip'); + throw new Error('[report parser] no report.json file found in the zip'); } const reportJson = await reportFile.async('string'); diff --git a/app/lib/parser/types.ts b/apps/backend/src/lib/parser/types.ts similarity index 100% rename from app/lib/parser/types.ts rename to apps/backend/src/lib/parser/types.ts diff --git a/app/lib/pw.ts b/apps/backend/src/lib/pw.ts similarity index 92% rename from app/lib/pw.ts rename to apps/backend/src/lib/pw.ts index d1973445..aba33969 100644 --- a/app/lib/pw.ts +++ b/apps/backend/src/lib/pw.ts @@ -1,15 +1,14 @@ import { exec } from 'node:child_process'; -import util from 'node:util'; -import { type UUID } from 'node:crypto'; -import path from 'node:path'; -import fs from 'node:fs/promises'; +import type { UUID } from 'node:crypto'; import { existsSync } from 'node:fs'; - -import { REPORTS_FOLDER, TMP_FOLDER } from './storage/constants'; -import { createDirectory } from './storage/folders'; -import { ReportMetadata } from './storage/types'; -import { defaultConfig } from './config'; -import { storage } from './storage'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import util from 'node:util'; +import { defaultConfig } from './config.js'; +import { storage } from './storage/index.js'; +import { REPORTS_FOLDER, TMP_FOLDER } from './storage/constants.js'; +import { createDirectory } from './storage/folders.js'; +import type { ReportMetadata } from './storage/types.js'; const execAsync = util.promisify(exec); @@ -22,7 +21,7 @@ export const isValidPlaywrightVersion = (version?: string): boolean => { export const generatePlaywrightReport = async ( reportId: UUID, - metadata: ReportMetadata, + metadata: ReportMetadata ): Promise<{ reportPath: string }> => { const { project, playwrightVersion } = metadata; diff --git a/apps/backend/src/lib/schemas/index.ts b/apps/backend/src/lib/schemas/index.ts new file mode 100644 index 00000000..4c495337 --- /dev/null +++ b/apps/backend/src/lib/schemas/index.ts @@ -0,0 +1,160 @@ +import { z } from 'zod'; + +export const UUIDSchema = z.uuid(); + +export const PaginationSchema = z.object({ + limit: z.coerce.number().min(1).max(100).default(10), + offset: z.coerce.number().min(0).default(0), +}); + +export const ReportMetadataSchema = z.looseObject({ + project: z.string().optional(), + title: z.string().optional(), + playwrightVersion: z.string().optional(), + testRun: z.string().optional(), + tags: z.array(z.string()).optional(), +}); + +export const ReportHistorySchema = z.looseObject({ + reportID: UUIDSchema, + project: z.string(), + title: z.string().optional(), + createdAt: z.string(), + reportUrl: z.string(), + size: z.string().optional(), + sizeBytes: z.number(), + stats: z + .object({ + total: z.number(), + expected: z.number(), + unexpected: z.number(), + flaky: z.number(), + skipped: z.number(), + ok: z.boolean(), + }) + .optional(), +}); + +export const ResultDetailsSchema = z.looseObject({ + resultID: UUIDSchema, + project: z.string().optional(), + title: z.string().optional(), + createdAt: z.string(), + size: z.string().optional(), + sizeBytes: z.number(), + playwrightVersion: z.string().optional(), + testRun: z.string().optional(), + shardCurrent: z.number().optional(), + shardTotal: z.number().optional(), + triggerReportGeneration: z.coerce.boolean().optional(), +}); + +export const GenerateReportRequestSchema = z.object({ + resultsIds: z.array(z.string()).min(1), + project: z.string().optional(), + playwrightVersion: z.string().optional(), + title: z.string().optional(), +}); + +export const GenerateReportResponseSchema = z.object({ + reportId: z.string(), + reportUrl: z.string(), + metadata: ReportMetadataSchema, +}); + +export const ListReportsQuerySchema = z.object({ + project: z.string().default(''), + search: z.string().default(''), + limit: z.coerce.number().min(1).max(100).optional(), + offset: z.coerce.number().min(0).optional(), +}); + +export const ListReportsResponseSchema = z.object({ + reports: z.array(ReportHistorySchema), + total: z.number(), +}); + +export const DeleteReportsRequestSchema = z.object({ + reportsIds: z.array(z.string()).min(1), +}); + +export const DeleteReportsResponseSchema = z.object({ + message: z.string(), + reportsIds: z.array(z.string()), +}); + +export const ListResultsQuerySchema = z.object({ + project: z.string().default(''), + search: z.string().default(''), + tags: z.string().optional(), // comma-separated + limit: z.coerce.number().min(1).max(100).optional(), + offset: z.coerce.number().min(0).optional(), +}); + +export const ListResultsResponseSchema = z.object({ + results: z.array(ResultDetailsSchema), + total: z.number(), +}); + +export const DeleteResultsRequestSchema = z.object({ + resultsIds: z.array(z.string()).min(1), +}); + +export const DeleteResultsResponseSchema = z.object({ + message: z.string(), + resultsIds: z.array(z.string()), +}); + +export const GetReportParamsSchema = z.object({ + id: z.string(), +}); + +export const GetReportResponseSchema = ReportHistorySchema; + +export const UploadResultResponseSchema = z.object({ + message: z.string(), + data: z.object({ + resultID: UUIDSchema, + generatedReport: GenerateReportResponseSchema.optional().nullable(), + testRun: z.string().optional(), + }), +}); + +export const ServerInfoSchema = z.object({ + dataFolderSizeinMB: z.string(), + numOfResults: z.number(), + resultsFolderSizeinMB: z.string(), + numOfReports: z.number(), + reportsFolderSizeinMB: z.string(), +}); + +export const ConfigSchema = z.looseObject({ + siteName: z.string().optional(), + logoUrl: z.string().optional(), + theme: z.string().optional(), +}); + +export const ErrorResponseSchema = z.object({ + error: z.string(), +}); + +export type GenerateReportRequest = z.infer; +export type GenerateReportResponse = z.infer; +export type ListReportsQuery = z.infer; +export type ListReportsResponse = z.infer; +export type DeleteReportsRequest = z.infer; +export type DeleteReportsResponse = z.infer; +export type ListResultsQuery = z.infer; +export type ListResultsResponse = z.infer; +export type DeleteResultsRequest = z.infer; +export type DeleteResultsResponse = z.infer; +export type GetReportParams = z.infer; +export type GetReportResponse = z.infer; +export type UploadResultResponse = z.infer; +export type ServerInfo = z.infer; +export type Config = z.infer; +export type ErrorResponse = z.infer; +export type ReportMetadata = z.infer; +export type ReportHistory = z.infer; +export type ResultDetails = z.infer; +export type Pagination = z.infer; diff --git a/app/lib/service/cache/config.ts b/apps/backend/src/lib/service/cache/config.ts similarity index 58% rename from app/lib/service/cache/config.ts rename to apps/backend/src/lib/service/cache/config.ts index d6527f3a..fc9db420 100644 --- a/app/lib/service/cache/config.ts +++ b/apps/backend/src/lib/service/cache/config.ts @@ -1,25 +1,26 @@ -import { storage } from '@/app/lib/storage'; -import { env } from '@/app/config/env'; -import { SiteWhiteLabelConfig } from '@/app/types'; -import { defaultConfig } from '@/app/lib/config'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import { defaultConfig } from '../../config.js'; +import { storage } from '../../storage/index.js'; + +const initiatedConfigDb = Symbol.for('playwright.reports.db.config'); +const instance = globalThis as typeof globalThis & { + [initiatedConfigDb]?: ConfigCache; +}; export class ConfigCache { - private static instance: ConfigCache; public initialized = false; public config: SiteWhiteLabelConfig | undefined; private constructor() {} public static getInstance() { - if (!ConfigCache.instance) { - ConfigCache.instance = new ConfigCache(); - } + instance[initiatedConfigDb] ??= new ConfigCache(); - return ConfigCache.instance; + return instance[initiatedConfigDb]; } public async init(): Promise { - if (this.initialized || !env.USE_SERVER_CACHE) { + if (this.initialized) { return; } @@ -42,12 +43,14 @@ export class ConfigCache { } public onChanged(config: SiteWhiteLabelConfig) { - if (!env.USE_SERVER_CACHE) { - return; - } - this.config = config; } + + public refresh(): void { + console.log('[config cache] refreshing cache'); + this.initialized = false; + this.config = undefined; + } } export const configCache = ConfigCache.getInstance(); diff --git a/app/lib/service/cron.ts b/apps/backend/src/lib/service/cron.ts similarity index 80% rename from app/lib/service/cron.ts rename to apps/backend/src/lib/service/cron.ts index d592dbbf..8abe575a 100644 --- a/app/lib/service/cron.ts +++ b/apps/backend/src/lib/service/cron.ts @@ -1,22 +1,23 @@ import { Cron } from 'croner'; +import { env } from '../../config/env.js'; +import { withError } from '../../lib/withError.js'; +import { service } from './index.js'; -import { service } from '@/app/lib/service'; -import { env } from '@/app/config/env'; -import { withError } from '@/app/lib/withError'; +const runningCron = Symbol.for('playwright.reports.cron.service'); +const instance = globalThis as typeof globalThis & { + [runningCron]?: CronService; +}; export class CronService { - private static instance: CronService; public initialized = false; private clearResultsJob: Cron | undefined; private clearReportsJob: Cron | undefined; public static getInstance() { - if (!CronService.instance) { - CronService.instance = new CronService(); - } + instance[runningCron] ??= new CronService(); - return CronService.instance; + return instance[runningCron]; } private constructor() { @@ -27,15 +28,12 @@ export class CronService { public async restart() { console.log('[cron-job] restarting cron tasks...'); - // Stop existing jobs this.clearResultsJob?.stop(); this.clearReportsJob?.stop(); - // Recreate jobs with new settings this.clearResultsJob = this.clearResultsTask(); this.clearReportsJob = this.clearReportsTask(); - // Reinitialize this.initialized = false; await this.init(); } @@ -53,8 +51,10 @@ export class CronService { const cfg = await service.getConfig(); const reportExpireDays = cfg.cron?.reportExpireDays || env.REPORT_EXPIRE_DAYS; const resultExpireDays = cfg.cron?.resultExpireDays || env.RESULT_EXPIRE_DAYS; - const reportExpireCronSchedule = cfg.cron?.reportExpireCronSchedule || env.REPORT_EXPIRE_CRON_SCHEDULE; - const resultExpireCronSchedule = cfg.cron?.resultExpireCronSchedule || env.RESULT_EXPIRE_CRON_SCHEDULE; + const reportExpireCronSchedule = + cfg.cron?.reportExpireCronSchedule || env.REPORT_EXPIRE_CRON_SCHEDULE; + const resultExpireCronSchedule = + cfg.cron?.resultExpireCronSchedule || env.RESULT_EXPIRE_CRON_SCHEDULE; console.log(`[cron-job] initiating cron tasks...`); for (const schedule of [ @@ -92,7 +92,11 @@ export class CronService { } private createJob(scheduleExpression: string, task: () => Promise) { - return new Cron(scheduleExpression, { catch: true, unref: true, paused: true, protect: true }, task); + return new Cron( + scheduleExpression, + { catch: true, unref: true, paused: true, protect: true }, + task + ); } private clearReportsTask() { @@ -112,7 +116,8 @@ export class CronService { const reportsOutput = await service.getReports(); const outdated = reportsOutput.reports.filter((report) => { - const createdDate = typeof report.createdAt === 'string' ? new Date(report.createdAt) : report.createdAt; + const createdDate = + typeof report.createdAt === 'string' ? new Date(report.createdAt) : report.createdAt; return expireDays ? this.isExpired(createdDate, expireDays) : false; }); @@ -143,7 +148,10 @@ export class CronService { const resultsOutput = await service.getResults(); const outdated = resultsOutput.results - .map((result) => ({ ...result, createdDate: new Date(result.createdAt) })) + .map((result) => ({ + ...result, + createdDate: new Date(result.createdAt), + })) .filter((result) => (expireDays ? this.isExpired(result.createdDate, expireDays) : false)); console.log(`[cron-job] found ${outdated.length} outdated results`); diff --git a/apps/backend/src/lib/service/db/db.ts b/apps/backend/src/lib/service/db/db.ts new file mode 100644 index 00000000..b77e9cb4 --- /dev/null +++ b/apps/backend/src/lib/service/db/db.ts @@ -0,0 +1,175 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import Database from 'better-sqlite3'; + +const initiatedDb = Symbol.for('playwright.reports.db'); +const instance = globalThis as typeof globalThis & { + [initiatedDb]?: Database.Database; +}; + +export function createDatabase(): Database.Database { + if (instance[initiatedDb]) { + return instance[initiatedDb]; + } + + const dbDir = path.join(process.cwd(), 'data'); + + if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }); + } + + const dbPath = path.join(dbDir, 'metadata.db'); + + console.log(`[db] creating database at ${dbPath}`); + + const db = new Database(dbPath, { + verbose: undefined, // for debugging: console.log + }); + + db.pragma('journal_mode = WAL'); // better concurrency + db.pragma('synchronous = NORMAL'); // faster writes, still safe with WAL + db.pragma('cache_size = -8000'); // 8MB page cache (balance of speed and memory) + db.pragma('mmap_size = 134217728'); // 128MB memory-mapped I/O + db.pragma('temp_store = MEMORY'); // store temporary tables in RAM + db.pragma('foreign_keys = ON'); // enforce referential integrity + db.pragma('auto_vacuum = INCREMENTAL'); // manage file size + + console.log('[db] database is configured'); + + initializeSchema(db); + instance[initiatedDb] = db; + + return db; +} + +function initializeSchema(db: Database.Database): void { + console.log('[db] initializing schema'); + + db.exec(` + CREATE TABLE IF NOT EXISTS results ( + resultID TEXT PRIMARY KEY, + project TEXT NOT NULL, + title TEXT, + createdAt TEXT NOT NULL, + size TEXT, + sizeBytes INTEGER, + metadata TEXT, -- JSON string for additional metadata + updatedAt TEXT DEFAULT CURRENT_TIMESTAMP + ); + + -- Indexes for common queries + CREATE INDEX IF NOT EXISTS idx_results_ids ON results(resultID); + CREATE INDEX IF NOT EXISTS idx_results_project ON results(project); + CREATE INDEX IF NOT EXISTS idx_results_createdAt ON results(createdAt DESC); + CREATE INDEX IF NOT EXISTS idx_results_updatedAt ON results(updatedAt DESC); + `); + + db.exec(` + CREATE TABLE IF NOT EXISTS reports ( + reportID TEXT PRIMARY KEY, + project TEXT NOT NULL, + title TEXT, + createdAt TEXT NOT NULL, + reportUrl TEXT NOT NULL, + size TEXT, + sizeBytes INTEGER, + stats TEXT, -- JSON string for report stats + metadata TEXT, -- JSON string for additional metadata + updatedAt TEXT DEFAULT CURRENT_TIMESTAMP + ); + + -- Indexes for common queries + CREATE INDEX IF NOT EXISTS idx_reports_ids ON reports(reportID); + CREATE INDEX IF NOT EXISTS idx_reports_project ON reports(project); + CREATE INDEX IF NOT EXISTS idx_reports_createdAt ON reports(createdAt DESC); + CREATE INDEX IF NOT EXISTS idx_reports_updatedAt ON reports(updatedAt DESC); + `); + + db.exec(` + CREATE TABLE IF NOT EXISTS cache_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updatedAt TEXT DEFAULT CURRENT_TIMESTAMP + ); + `); + + console.log('[db] schema initialized'); +} + +export function getDatabase(): Database.Database { + if (!instance[initiatedDb]) { + return createDatabase(); + } + + return instance[initiatedDb]; +} + +export function closeDatabase(): void { + if (instance[initiatedDb]) { + console.log('[db] closing database connection'); + const db = getDatabase(); + + db.close(); + instance[initiatedDb] = undefined; + } +} + +export function getDatabaseStats(): { + results: number; + reports: number; + sizeOnDisk: string; + estimatedRAM: string; +} { + const db = getDatabase(); + + const resultsCount = db.prepare('SELECT COUNT(*) as count FROM results').get() as { + count: number; + }; + const reportsCount = db.prepare('SELECT COUNT(*) as count FROM reports').get() as { + count: number; + }; + + const stats = { + pageCount: db.pragma('page_count', { simple: true }) as number, + pageSize: db.pragma('page_size', { simple: true }) as number, + cacheSize: db.pragma('cache_size', { simple: true }) as number, + }; + + const dbSizeBytes = stats.pageCount * stats.pageSize; + const cacheSizeBytes = Math.abs(stats.cacheSize) * (stats.cacheSize < 0 ? 1024 : stats.pageSize); + + return { + results: resultsCount.count, + reports: reportsCount.count, + sizeOnDisk: `${(dbSizeBytes / 1024 / 1024).toFixed(2)} MB`, + estimatedRAM: `~${(cacheSizeBytes / 1024 / 1024).toFixed(2)} MB`, + }; +} + +export function clearAll(): void { + const db = getDatabase(); + + console.log('[db] clearing all data'); + + db.exec(` + DELETE FROM results; + DELETE FROM reports; + DELETE FROM cache_metadata; + `); + + db.exec('VACUUM;'); + + console.log('[db] cleared'); +} + +export function optimizeDB(): void { + const db = getDatabase(); + + console.log('[db] optimizing database'); + + db.exec('ANALYZE;'); + db.exec('PRAGMA incremental_vacuum;'); + + console.log('[db] optimization complete'); +} diff --git a/apps/backend/src/lib/service/db/forceInit.ts b/apps/backend/src/lib/service/db/forceInit.ts new file mode 100644 index 00000000..7cfff516 --- /dev/null +++ b/apps/backend/src/lib/service/db/forceInit.ts @@ -0,0 +1,21 @@ +import { reportDb, resultDb } from './index.js'; +import { withError } from '../../withError.js'; + +export const forceInitDatabase = async () => { + const { error: cleanupError } = await withError( + Promise.all([reportDb.clear(), resultDb.clear()]) + ); + + if (cleanupError) { + throw new Error(`failed to clear db: ${cleanupError.message}`); + } + + reportDb.initialized = false; + resultDb.initialized = false; + + const { error } = await withError(Promise.all([reportDb.init(), resultDb.init()])); + + if (error) { + throw new Error(`failed to initialize db: ${error.message}`); + } +}; diff --git a/apps/backend/src/lib/service/db/index.ts b/apps/backend/src/lib/service/db/index.ts new file mode 100644 index 00000000..1e896822 --- /dev/null +++ b/apps/backend/src/lib/service/db/index.ts @@ -0,0 +1,4 @@ +export * from './db.js'; +export * from './forceInit.js'; +export * from './reports.sqlite.js'; +export * from './results.sqlite.js'; diff --git a/apps/backend/src/lib/service/db/reports.sqlite.ts b/apps/backend/src/lib/service/db/reports.sqlite.ts new file mode 100644 index 00000000..7915554c --- /dev/null +++ b/apps/backend/src/lib/service/db/reports.sqlite.ts @@ -0,0 +1,335 @@ +import type Database from 'better-sqlite3'; +import { storage } from '../../storage/index.js'; +import type { ReadReportsInput, ReadReportsOutput, ReportHistory } from '../../storage/types.js'; +import { withError } from '../../withError.js'; +import { getDatabase } from './db.js'; + +const initiatedReportsDb = Symbol.for('playwright.reports.db.reports'); +const instance = globalThis as typeof globalThis & { + [initiatedReportsDb]?: ReportDatabase; +}; + +export class ReportDatabase { + public initialized = false; + private readonly db = getDatabase(); + + private readonly insertStmt: Database.Statement< + [string, string, string | null, string, string, string | null, number, string | null, string] + >; + private readonly updateStmt: Database.Statement< + [string, string | null, string, string | null, number, string | null, string, string] + >; + private readonly deleteStmt: Database.Statement<[string]>; + private readonly getByIDStmt: Database.Statement<[string]>; + private readonly getAllStmt: Database.Statement<[]>; + private readonly getByProjectStmt: Database.Statement<[string]>; + private readonly searchStmt: Database.Statement<[string, string, string, string]>; + + private constructor() { + this.insertStmt = this.db.prepare(` + INSERT OR REPLACE INTO reports (reportID, project, title, createdAt, reportUrl, size, sizeBytes, stats, metadata, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + `); + + this.updateStmt = this.db.prepare(` + UPDATE reports + SET project = ?, title = ?, reportUrl = ?, size = ?, sizeBytes = ?, stats = ?, metadata = ?, updatedAt = CURRENT_TIMESTAMP + WHERE reportID = ? + `); + + this.deleteStmt = this.db.prepare('DELETE FROM reports WHERE reportID = ?'); + + this.getByIDStmt = this.db.prepare('SELECT * FROM reports WHERE reportID = ?'); + + this.getAllStmt = this.db.prepare('SELECT * FROM reports ORDER BY createdAt DESC'); + + this.getByProjectStmt = this.db.prepare( + 'SELECT * FROM reports WHERE project = ? ORDER BY createdAt DESC' + ); + + this.searchStmt = this.db.prepare(` + SELECT * FROM reports + WHERE title LIKE ? OR reportID LIKE ? OR project LIKE ? OR metadata LIKE ? + ORDER BY createdAt DESC + `); + } + + public static getInstance(): ReportDatabase { + instance[initiatedReportsDb] ??= new ReportDatabase(); + + return instance[initiatedReportsDb]; + } + + public async init() { + if (this.initialized) { + return; + } + + console.log('[report db] initializing SQLite for reports'); + const { result, error } = await withError(storage.readReports()); + + if (error) { + console.error('[report db] failed to read reports:', error); + + return; + } + + if (!result?.reports?.length) { + console.log('[report db] no reports to store'); + this.initialized = true; + + return; + } + + console.log(`[report db] caching ${result.reports.length} reports`); + + const insertMany = this.db.transaction((reports: ReportHistory[]) => { + for (const report of reports) { + this.insertReport(report); + } + }); + + insertMany(result.reports as ReportHistory[]); + + this.initialized = true; + console.log('[report db] initialization complete'); + } + + private insertReport(report: ReportHistory): void { + const { reportID, project, title, createdAt, reportUrl, size, sizeBytes, stats, ...metadata } = + report; + + const createdAtStr = + createdAt instanceof Date + ? createdAt.toDateString() + : typeof createdAt === 'string' + ? createdAt + : String(createdAt); + + this.insertStmt.run( + reportID, + project || '', + title || null, + createdAtStr, + reportUrl, + size || null, + sizeBytes || 0, + stats ? JSON.stringify(stats) : null, + JSON.stringify(metadata) + ); + } + + public onDeleted(reportIds: string[]) { + console.log(`[report db] deleting ${reportIds.length} reports`); + + const deleteMany = this.db.transaction((ids: string[]) => { + for (const id of ids) { + this.deleteStmt.run(id); + } + }); + + deleteMany(reportIds); + } + + public onCreated(report: ReportHistory) { + console.log(`[report db] adding report ${report.reportID}`); + this.insertReport(report); + } + + public onUpdated(report: ReportHistory) { + console.log(`[report db] updating report ${report.reportID}`); + const { reportID, project, title, reportUrl, size, sizeBytes, stats, ...metadata } = report; + + this.updateStmt.run( + project || '', + title || null, + reportUrl, + size || null, + sizeBytes || 0, + stats ? JSON.stringify(stats) : null, + JSON.stringify(metadata), + reportID + ); + } + + public getAll(): ReportHistory[] { + const rows = this.getAllStmt.all() as Array<{ + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + }>; + + return rows.map(this.rowToReport); + } + + public getByID(reportID: string): ReportHistory | undefined { + const row = this.getByIDStmt.get(reportID) as + | { + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + } + | undefined; + + return row ? this.rowToReport(row) : undefined; + } + + public getByProject(project: string): ReportHistory[] { + const rows = this.getByProjectStmt.all(project) as Array<{ + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + }>; + + return rows.map(this.rowToReport); + } + + public search(query: string): ReportHistory[] { + const searchPattern = `%${query}%`; + const rows = this.searchStmt.all( + searchPattern, + searchPattern, + searchPattern, + searchPattern + ) as Array<{ + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + }>; + + return rows.map(this.rowToReport); + } + + public getCount(): number { + const result = this.db.prepare('SELECT COUNT(*) as count FROM reports').get() as { + count: number; + }; + + return result.count; + } + + public clear(): void { + console.log('[report db] clearing all reports'); + this.db.prepare('DELETE FROM reports').run(); + } + + public query(input?: ReadReportsInput): ReadReportsOutput { + let query = 'SELECT * FROM reports'; + const params: string[] = []; + const conditions: string[] = []; + + if (input?.ids && input.ids.length > 0) { + conditions.push(`reportID IN (${input.ids.map(() => '?').join(', ')})`); + params.push(...input.ids); + } + + if (input?.project) { + conditions.push('project = ?'); + params.push(input.project); + } + + if (input?.search?.trim()) { + const searchTerm = `%${input.search.toLowerCase().trim()}%`; + + conditions.push( + '(LOWER(title) LIKE ? OR LOWER(reportID) LIKE ? OR LOWER(project) LIKE ? OR LOWER(metadata) LIKE ?)' + ); + params.push(searchTerm, searchTerm, searchTerm, searchTerm); + } + + if (conditions.length > 0) { + query += ` WHERE ${conditions.join(' AND ')}`; + } + + query += ' ORDER BY createdAt DESC'; + + const countQuery = query.replace('SELECT *', 'SELECT COUNT(*) as count'); + const countResult = this.db.prepare(countQuery).get(...params) as { + count: number; + }; + const total = countResult.count; + + if (input?.pagination) { + query += ' LIMIT ? OFFSET ?'; + params.push(input.pagination.limit.toString(), input.pagination.offset.toString()); + } + + const rows = this.db.prepare(query).all(...params) as Array<{ + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + }>; + + return { + reports: rows.map((row) => this.rowToReport(row)), + total, + }; + } + + public async refresh(): Promise { + console.log('[report db] refreshing cache'); + this.clear(); + this.initialized = false; + // Re-initialize immediately by reading from filesystem + await this.init(); + } + + private rowToReport(row: { + reportID: string; + project: string; + title: string | null; + createdAt: string; + reportUrl: string; + size: string | null; + sizeBytes: number; + stats: string | null; + metadata: string; + }): ReportHistory { + const metadata = JSON.parse(row.metadata || '{}'); + const stats = row.stats ? JSON.parse(row.stats) : undefined; + + return { + reportID: row.reportID, + project: row.project, + title: row.title || undefined, + createdAt: row.createdAt, + reportUrl: row.reportUrl, + size: row.size || undefined, + sizeBytes: row.sizeBytes, + stats, + ...metadata, + }; + } +} + +export const reportDb = ReportDatabase.getInstance(); diff --git a/apps/backend/src/lib/service/db/results.sqlite.ts b/apps/backend/src/lib/service/db/results.sqlite.ts new file mode 100644 index 00000000..a4e2d3fe --- /dev/null +++ b/apps/backend/src/lib/service/db/results.sqlite.ts @@ -0,0 +1,321 @@ +import type Database from 'better-sqlite3'; +import { storage } from '../../storage/index.js'; +import type { ReadResultsInput, ReadResultsOutput, Result } from '../../storage/types.js'; +import { withError } from '../../withError.js'; +import { getDatabase } from './db.js'; + +const initiatedResultsDb = Symbol.for('playwright.reports.db.results'); +const instance = globalThis as typeof globalThis & { + [initiatedResultsDb]?: ResultDatabase; +}; + +export class ResultDatabase { + public initialized = false; + private readonly db = getDatabase(); + + private readonly insertStmt: Database.Statement< + [string, string, string | null, string, string | null, number, string] + >; + private readonly updateStmt: Database.Statement< + [string, string | null, string | null, number, string, string] + >; + private readonly deleteStmt: Database.Statement<[string]>; + private readonly getByIDStmt: Database.Statement<[string]>; + private readonly getAllStmt: Database.Statement<[]>; + private readonly getByProjectStmt: Database.Statement<[string]>; + private readonly searchStmt: Database.Statement<[string, string, string, string]>; + + private constructor() { + this.insertStmt = this.db.prepare(` + INSERT OR REPLACE INTO results (resultID, project, title, createdAt, size, sizeBytes, metadata, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + `); + + this.updateStmt = this.db.prepare(` + UPDATE results + SET project = ?, title = ?, size = ?, sizeBytes = ?, metadata = ?, updatedAt = CURRENT_TIMESTAMP + WHERE resultID = ? + `); + + this.deleteStmt = this.db.prepare('DELETE FROM results WHERE resultID = ?'); + + this.getByIDStmt = this.db.prepare('SELECT * FROM results WHERE resultID = ?'); + + this.getAllStmt = this.db.prepare('SELECT * FROM results ORDER BY createdAt DESC'); + + this.getByProjectStmt = this.db.prepare( + 'SELECT * FROM results WHERE project = ? ORDER BY createdAt DESC' + ); + + this.searchStmt = this.db.prepare(` + SELECT * FROM results + WHERE title LIKE ? OR resultID LIKE ? OR project LIKE ? OR metadata LIKE ? + ORDER BY createdAt DESC + `); + } + + public static getInstance(): ResultDatabase { + instance[initiatedResultsDb] ??= new ResultDatabase(); + + return instance[initiatedResultsDb]; + } + + public async init() { + if (this.initialized) { + return; + } + + console.log('[result db] initializing SQLite for results'); + const { result: resultsResponse, error } = await withError(storage.readResults()); + + if (error) { + console.error('[result db] failed to read results:', error); + + return; + } + + if (!resultsResponse?.results?.length) { + console.log('[result db] no results to store'); + this.initialized = true; + + return; + } + + console.log(`[result db] caching ${resultsResponse.results.length} results`); + + const insertMany = this.db.transaction((results: Result[]) => { + for (const result of results) { + this.insertResult(result); + } + }); + + insertMany(resultsResponse.results); + + this.initialized = true; + console.log('[result db] initialization complete'); + } + + private insertResult(result: Result): void { + const { resultID, project, title, createdAt, size, sizeBytes, ...metadata } = result; + + this.insertStmt.run( + resultID, + project || '', + title || null, + createdAt, + size || null, + sizeBytes || 0, + JSON.stringify(metadata) + ); + } + + public onDeleted(resultIds: string[]) { + console.log(`[result db] deleting ${resultIds.length} results`); + + const deleteMany = this.db.transaction((ids: string[]) => { + for (const id of ids) { + this.deleteStmt.run(id); + } + }); + + deleteMany(resultIds); + } + + public onCreated(result: Result) { + console.log(`[result db] adding result ${result.resultID}`); + this.insertResult(result); + } + + public onUpdated(result: Result) { + console.log(`[result db] updating result ${result.resultID}`); + const { resultID, project, title, size, sizeBytes, ...metadata } = result; + + this.updateStmt.run( + project || '', + title || null, + size || null, + sizeBytes || 0, + JSON.stringify(metadata), + resultID + ); + } + + public getAll(): Result[] { + const rows = this.getAllStmt.all() as Array<{ + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + }>; + + return rows.map(this.rowToResult); + } + + public getByID(resultID: string): Result | undefined { + const row = this.getByIDStmt.get(resultID) as + | { + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + } + | undefined; + + return row ? this.rowToResult(row) : undefined; + } + + public getByProject(project: string): Result[] { + const rows = this.getByProjectStmt.all(project) as Array<{ + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + }>; + + return rows.map(this.rowToResult); + } + + public search(query: string): Result[] { + const searchPattern = `%${query}%`; + const rows = this.searchStmt.all( + searchPattern, + searchPattern, + searchPattern, + searchPattern + ) as Array<{ + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + }>; + + return rows.map(this.rowToResult); + } + + public getCount(): number { + const result = this.db.prepare('SELECT COUNT(*) as count FROM results').get() as { + count: number; + }; + + return result.count; + } + + public clear(): void { + console.log('[result db] clearing all results'); + this.db.prepare('DELETE FROM results').run(); + } + + public query(input?: ReadResultsInput): ReadResultsOutput { + let query = 'SELECT * FROM results'; + const params: string[] = []; + const conditions: string[] = []; + + if (input?.project) { + conditions.push('project = ?'); + params.push(input.project); + } + + if (input?.testRun) { + conditions.push('metadata LIKE ?'); + params.push(`%"testRun":"${input.testRun}"%`); + } + + if (input?.tags && input.tags.length > 0) { + console.log('Filtering by tags:', input.tags); + + for (const tag of input.tags) { + const [key, value] = tag.split(':').map((part) => part.trim()); + + conditions.push('metadata LIKE ?'); + params.push(`%"${key}":"${value}"%`); + } + } + + if (input?.search?.trim()) { + const searchTerm = `%${input.search.toLowerCase().trim()}%`; + + conditions.push( + '(LOWER(title) LIKE ? OR LOWER(resultID) LIKE ? OR LOWER(project) LIKE ? OR LOWER(metadata) LIKE ?)' + ); + params.push(searchTerm, searchTerm, searchTerm, searchTerm); + } + + if (conditions.length > 0) { + query += ` WHERE ${conditions.join(' AND ')}`; + } + + query += ' ORDER BY createdAt DESC'; + + const countQuery = query.replace('SELECT *', 'SELECT COUNT(*) as count'); + const countResult = this.db.prepare(countQuery).get(...params) as { + count: number; + }; + const total = countResult.count; + + if (input?.pagination) { + query += ' LIMIT ? OFFSET ?'; + params.push(input.pagination.limit.toString(), input.pagination.offset.toString()); + } + + console.log('Executing query:', query, 'with params:', params); + + const rows = this.db.prepare(query).all(...params) as Array<{ + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + }>; + + return { + results: rows.map((row) => this.rowToResult(row)), + total, + }; + } + + public async refresh(): Promise { + console.log('[result db] refreshing cache'); + this.clear(); + this.initialized = false; + // Re-initialize immediately by reading from filesystem + await this.init(); + } + + private rowToResult(row: { + resultID: string; + project: string; + title: string | null; + createdAt: string; + size: string | null; + sizeBytes: number; + metadata: string; + }): Result { + const metadata = JSON.parse(row.metadata || '{}'); + + return { + resultID: row.resultID, + project: row.project, + title: row.title || undefined, + createdAt: row.createdAt, + size: row.size || undefined, + sizeBytes: row.sizeBytes, + ...metadata, + }; + } +} + +export const resultDb = ResultDatabase.getInstance(); diff --git a/apps/backend/src/lib/service/index.ts b/apps/backend/src/lib/service/index.ts new file mode 100644 index 00000000..89cab81f --- /dev/null +++ b/apps/backend/src/lib/service/index.ts @@ -0,0 +1,375 @@ +import { type PassThrough, Readable } from 'node:stream'; +import { env } from '../../config/env.js'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import { defaultConfig } from '../config.js'; +import { serveReportRoute } from '../constants.js'; +import { isValidPlaywrightVersion } from '../pw.js'; +import { DEFAULT_STREAM_CHUNK_SIZE } from '../storage/constants.js'; +import { bytesToString, getUniqueProjectsList } from '../storage/format.js'; +import { + type ReadReportsInput, + type ReadResultsInput, + type ReadResultsOutput, + type ReportMetadata, + type ReportPath, + type ResultDetails, + type ServerDataInfo, + storage, +} from '../storage/index.js'; +import type { S3 } from '../storage/s3.js'; +import { withError } from '../withError.js'; +import { configCache } from './cache/config.js'; +import { reportDb, resultDb } from './db/index.js'; +import { lifecycle } from './lifecycle.js'; + +class Service { + private static instance: Service | null = null; + + public static getInstance(): Service { + Service.instance ??= new Service(); + return Service.instance; + } + + public async getReports(input?: ReadReportsInput) { + console.log(`[service] getReports`); + + return reportDb.query(input); + } + + public async getReport(id: string, path?: string) { + console.log(`[service] getReport ${id}`); + + const report = reportDb.getByID(id); + + if (!report && path) { + console.warn(`[service] getReport ${id} - not found in db, fetching from storage`); + const { result: reportFromStorage, error } = await withError(storage.readReport(id, path)); + + if (error) { + console.error(`[service] getReport ${id} - error fetching from storage: ${error.message}`); + throw error; + } + + if (!reportFromStorage) { + throw new Error(`report ${id} not found`); + } + + return reportFromStorage; + } + + if (!report) { + throw new Error(`report ${id} not found`); + } + + return report; + } + + private async findLatestPlaywrightVersionFromResults(resultIds: string[]) { + for (const resultId of resultIds) { + const { result: results, error } = await withError(this.getResults({ search: resultId })); + + if (error || !results) { + continue; + } + + const [latestResult] = results.results; + + if (!latestResult) { + continue; + } + + const latestVersion = latestResult?.playwrightVersion; + + if (latestVersion) { + return latestVersion; + } + } + } + + private async findLatestPlaywrightVersion(resultIds: string[]) { + const versionFromResults = await this.findLatestPlaywrightVersionFromResults(resultIds); + + if (versionFromResults) { + return versionFromResults; + } + + // just in case version not found in results, we can try to get it from latest reports + const { result: reportsArray, error } = await withError( + this.getReports({ pagination: { limit: 10, offset: 0 } }) + ); + + if (error || !reportsArray) { + return ''; + } + + const reportWithVersion = reportsArray.reports.find( + (report) => !!report.metadata?.playwrightVersion + ); + + if (!reportWithVersion) { + return ''; + } + + return reportWithVersion.metadata.playwrightVersion; + } + + public async generateReport( + resultsIds: string[], + metadata?: ReportMetadata + ): Promise<{ + reportId: string; + reportUrl: string; + metadata: ReportMetadata; + }> { + const version = isValidPlaywrightVersion(metadata?.playwrightVersion) + ? metadata?.playwrightVersion + : await this.findLatestPlaywrightVersion(resultsIds); + + const metadataWithVersion = { + ...(metadata ?? {}), + playwrightVersion: version ?? '', + }; + + const { reportId, reportPath } = await storage.generateReport(resultsIds, metadataWithVersion); + + console.log(`[service] reading report ${reportId} from path: ${reportPath}`); + const { result: report, error } = await withError(storage.readReport(reportId, reportPath)); + + if (error) { + throw new Error(`Failed to read generated report: ${error.message}`); + } + + if (!report) { + throw new Error(`Generated report ${reportId} not found`); + } + + reportDb.onCreated(report); + + const projectPath = metadata?.project ? `${encodeURI(metadata.project)}/` : ''; + const reportUrl = `${serveReportRoute}/${projectPath}${reportId}/index.html`; + + return { reportId, reportUrl, metadata: metadataWithVersion }; + } + + public async deleteReports(reportIDs: string[]) { + const entries: ReportPath[] = []; + + for (const id of reportIDs) { + const report = await this.getReport(id); + + entries.push({ reportID: id, project: report.project }); + } + + const { error } = await withError(storage.deleteReports(entries)); + + if (error) { + throw error; + } + + reportDb.onDeleted(reportIDs); + } + + public async getReportsProjects(): Promise { + const { reports } = await this.getReports(); + const projects = getUniqueProjectsList(reports); + + return projects; + } + + public async getResults(input?: ReadResultsInput): Promise { + console.log(`[results service] getResults`); + console.log(`querying results:`); + console.log(JSON.stringify(input, null, 2)); + + return resultDb.query(input); + } + + public async deleteResults(resultIDs: string[]): Promise { + console.log(`[service] deleteResults`); + console.log(`deleting results:`, resultIDs); + + const { error } = await withError(storage.deleteResults(resultIDs)); + + if (error) { + console.error(`[service] deleteResults - storage deletion failed:`, error); + throw error; + } + + console.log( + `[service] deleteResults - storage deletion successful, removing from database cache` + ); + resultDb.onDeleted(resultIDs); + console.log(`[service] deleteResults - database cache cleanup completed`); + } + + public async getPresignedUrl(fileName: string): Promise { + console.log(`[service] getPresignedUrl for ${fileName}`); + + if (env.DATA_STORAGE !== 's3') { + console.log(`[service] fs storage detected, no presigned URL needed`); + + return ''; + } + + console.log(`[service] s3 detected, generating presigned URL`); + + const { result: presignedUrl, error } = await withError( + (storage as S3).generatePresignedUploadUrl(fileName) + ); + + if (error) { + console.error(`[service] getPresignedUrl | error: ${error.message}`); + + return ''; + } + + if (!presignedUrl) { + console.error(`[service] getPresignedUrl | presigned URL is null or undefined`); + + return ''; + } + + return presignedUrl; + } + + public async saveResult( + filename: string, + stream: PassThrough, + presignedUrl?: string, + contentLength?: string + ) { + if (!presignedUrl) { + console.log(`[service] saving result`); + + return await storage.saveResult(filename, stream); + } + + console.log(`[service] using direct upload via presigned URL`, presignedUrl); + + const { error } = await withError( + fetch(presignedUrl, { + method: 'PUT', + body: Readable.toWeb(stream, { + strategy: { + highWaterMark: DEFAULT_STREAM_CHUNK_SIZE, + }, + }), + headers: { + 'Content-Type': 'application/zip', + 'Content-Length': contentLength, + }, + duplex: 'half', + } as RequestInit) + ); + + if (error) { + console.error(`[s3] saveResult | error: ${error.message}`); + throw error; + } + } + + public async saveResultDetails(resultID: string, resultDetails: ResultDetails, size: number) { + const result = await storage.saveResultDetails(resultID, resultDetails, size); + + resultDb.onCreated(result); + + return result; + } + + public async getResultsProjects(): Promise { + const { results } = await this.getResults(); + const projects = getUniqueProjectsList(results); + + const reportProjects = await this.getReportsProjects(); + + return Array.from(new Set([...projects, ...reportProjects])); + } + + public async getResultsTags(project?: string): Promise { + const { results } = await this.getResults(project ? { project } : undefined); + + const notMetadataKeys = ['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project']; + const allTags = new Set(); + + results.forEach((result) => { + Object.entries(result).forEach(([key, value]) => { + if (!notMetadataKeys.includes(key) && value !== undefined && value !== null) { + allTags.add(`${key}: ${value}`); + } + }); + }); + + return Array.from(allTags).sort(); + } + + public async getServerInfo(): Promise { + console.log(`[service] getServerInfo`); + const canCalculateFromCache = + lifecycle.isInitialized() && reportDb.initialized && resultDb.initialized; + + if (!canCalculateFromCache) { + return await storage.getServerDataInfo(); + } + + const reports = reportDb.getAll(); + const results = resultDb.getAll(); + + const getTotalSizeBytes = (entity: T) => + entity.reduce((total, item) => total + item.sizeBytes, 0); + + const reportsFolderSize = getTotalSizeBytes(reports); + const resultsFolderSize = getTotalSizeBytes(results); + const dataFolderSize = reportsFolderSize + resultsFolderSize; + + return { + dataFolderSizeinMB: bytesToString(dataFolderSize), + numOfResults: results.length, + resultsFolderSizeinMB: bytesToString(resultsFolderSize), + numOfReports: reports.length, + reportsFolderSizeinMB: bytesToString(reportsFolderSize), + }; + } + + public async getConfig() { + if (lifecycle.isInitialized() && configCache.initialized) { + const cached = configCache.config; + + if (cached) { + console.log(`[service] using cached config`); + + return cached; + } + } + + const { result, error } = await storage.readConfigFile(); + + if (error) console.error(`[service] getConfig | error: ${error.message}`); + + return { ...defaultConfig, ...(result ?? {}) }; + } + + public async updateConfig(config: Partial) { + console.log(`[service] updateConfig`, config); + const { result, error } = await storage.saveConfigFile(config); + + if (error) { + throw error; + } + + configCache.onChanged(result); + + return result; + } + + public async refreshCache() { + console.log(`[service] refreshCache`); + + await reportDb.refresh(); + await resultDb.refresh(); + configCache.refresh(); + + return { message: 'cache refreshed successfully' }; + } +} + +export const service = Service.getInstance(); diff --git a/app/lib/service/jira.ts b/apps/backend/src/lib/service/jira.ts similarity index 88% rename from app/lib/service/jira.ts rename to apps/backend/src/lib/service/jira.ts index 28018b04..e4603932 100644 --- a/app/lib/service/jira.ts +++ b/apps/backend/src/lib/service/jira.ts @@ -1,5 +1,5 @@ -import { env } from '@/app/config/env'; -import { JiraConfig } from '@/app/types'; +import { env } from '../../config/env.js'; +import type { JiraConfig } from '@playwright-reports/shared'; export interface JiraIssueFields { summary: string; @@ -18,7 +18,7 @@ export interface JiraIssueFields { }; issuetype: { name?: string; id?: string }; project: { key: string }; - [key: string]: any; + [key: string]: unknown; } export interface JiraCreateIssueRequest { @@ -36,13 +36,16 @@ export interface JiraErrorResponse { errors: Record; } +const initiatedJira = Symbol.for('playwright.reports.jira'); +const _instance = globalThis as typeof globalThis & { + [initiatedJira]?: JiraService; +}; + export class JiraService { - private static instance: JiraService; private baseUrl: string; private auth: string; private constructor(jiraConfig?: JiraConfig) { - // Use config if provided, otherwise fall back to environment variables const config = jiraConfig || { baseUrl: env.JIRA_BASE_URL, email: env.JIRA_EMAIL, @@ -56,7 +59,7 @@ export class JiraService { if (!this.baseUrl || !email || !apiToken) { throw new Error( - 'Jira configuration is incomplete. Please configure Jira settings in the admin panel or set JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.', + 'Jira configuration is incomplete. Please configure Jira settings in the admin panel or set JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.' ); } @@ -64,21 +67,19 @@ export class JiraService { } public static getInstance(jiraConfig?: JiraConfig): JiraService { - if (!JiraService.instance) { - JiraService.instance = new JiraService(jiraConfig); - } + _instance[initiatedJira] ??= new JiraService(jiraConfig); - return JiraService.instance; + return _instance[initiatedJira]; } public static resetInstance(): void { - JiraService.instance = undefined as any; + _instance[initiatedJira] = undefined; } private async makeRequest( endpoint: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', - body?: any, + body?: any ): Promise { const url = `${this.baseUrl}/rest/api/3${endpoint}`; @@ -131,7 +132,7 @@ export class JiraService { name: string; path: string; contentType: string; - }>, + }> ): Promise { const issueTypes = await this.getIssueTypes(projectKey); @@ -144,14 +145,16 @@ export class JiraService { availableIssueTypes = project.issueTypes; } } catch { - console.warn(`Could not fetch project-specific issue types for ${projectKey}, using global issue types`); + console.warn( + `Could not fetch project-specific issue types for ${projectKey}, using global issue types` + ); } const issueTypeObj = availableIssueTypes.find((it: any) => it.name === issueType); if (!issueTypeObj) { throw new Error( - `Issue type '${issueType}' not found. Available issue types: ${availableIssueTypes.map((it: any) => it.name).join(', ')}`, + `Issue type '${issueType}' not found. Available issue types: ${availableIssueTypes.map((it: any) => it.name).join(', ')}` ); } @@ -201,7 +204,11 @@ export class JiraService { console.log('Jira request body:', JSON.stringify(requestBody, null, 2)); - const issueResponse = await this.makeRequest('/issue', 'POST', requestBody); + const issueResponse = await this.makeRequest( + '/issue', + 'POST', + requestBody + ); if (attachments && attachments.length > 0) { for (const attachment of attachments) { @@ -226,10 +233,10 @@ export class JiraService { name: string; path: string; contentType: string; - }, + } ): Promise { try { - const { storage } = await import('@/app/lib/storage'); + const { storage } = await import('../storage/index.js'); let fileName = attachment.name; @@ -243,7 +250,8 @@ export class JiraService { const fileContent = await storage.readFile(attachment.path, attachment.contentType); - const fileBuffer = typeof fileContent === 'string' ? Buffer.from(fileContent, 'utf-8') : fileContent; + const fileBuffer = + typeof fileContent === 'string' ? Buffer.from(fileContent, 'utf-8') : fileContent; const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`; @@ -271,7 +279,7 @@ export class JiraService { const errorText = await response.text(); throw new Error( - `Failed to attach file to JIRA issue: ${response.status} ${response.statusText} - ${errorText}`, + `Failed to attach file to JIRA issue: ${response.status} ${response.statusText} - ${errorText}` ); } diff --git a/apps/backend/src/lib/service/lifecycle.ts b/apps/backend/src/lib/service/lifecycle.ts new file mode 100644 index 00000000..9404bf73 --- /dev/null +++ b/apps/backend/src/lib/service/lifecycle.ts @@ -0,0 +1,70 @@ +import { configCache } from './cache/config.js'; +import { cronService } from './cron.js'; +import { reportDb, resultDb } from './db/index.js'; + +const createdLifecycle = Symbol.for('playwright.reports.lifecycle'); +const instance = globalThis as typeof globalThis & { + [createdLifecycle]?: Lifecycle; +}; + +export class Lifecycle { + private initialized = false; + private initPromise?: Promise; + + public static getInstance(): Lifecycle { + instance[createdLifecycle] ??= new Lifecycle(); + + return instance[createdLifecycle]; + } + + public async initialize(): Promise { + if (this.initialized) return; + + this.initPromise ??= this._performInitialization(); + + return this.initPromise; + } + + private async _performInitialization(): Promise { + console.log('[lifecycle] Starting application initialization'); + + try { + await Promise.all([configCache.init(), reportDb.init(), resultDb.init()]); + console.log('[lifecycle] Databases initialized successfully'); + + if (!cronService.initialized) { + await cronService.init(); + console.log('[lifecycle] Cron service initialized successfully'); + } + + this.initialized = true; + console.log('[lifecycle] Application initialization complete'); + } catch (error) { + console.error('[lifecycle] Initialization failed:', error); + throw error; + } + } + + public isInitialized(): boolean { + return this.initialized; + } + + public async cleanup(): Promise { + if (!this.initialized) return; + + console.log('[lifecycle] Starting application cleanup'); + + try { + if (cronService.initialized) { + await cronService.restart(); + console.log('[lifecycle] Cron service stopped'); + } + + console.log('[lifecycle] Application cleanup complete'); + } catch (error) { + console.error('[lifecycle] Cleanup failed:', error); + } + } +} + +export const lifecycle = Lifecycle.getInstance(); diff --git a/app/lib/storage/batch.ts b/apps/backend/src/lib/storage/batch.ts similarity index 90% rename from app/lib/storage/batch.ts rename to apps/backend/src/lib/storage/batch.ts index 3bf409d2..447d8200 100644 --- a/app/lib/storage/batch.ts +++ b/apps/backend/src/lib/storage/batch.ts @@ -2,7 +2,7 @@ export async function processBatch( ctx: unknown, items: T[], batchSize: number, - asyncAction: (item: T) => Promise, + asyncAction: (item: T) => Promise ): Promise { const results: R[] = []; diff --git a/app/lib/storage/constants.ts b/apps/backend/src/lib/storage/constants.ts similarity index 100% rename from app/lib/storage/constants.ts rename to apps/backend/src/lib/storage/constants.ts diff --git a/app/lib/storage/file.ts b/apps/backend/src/lib/storage/file.ts similarity index 89% rename from app/lib/storage/file.ts rename to apps/backend/src/lib/storage/file.ts index f5ce904a..803b435a 100644 --- a/app/lib/storage/file.ts +++ b/apps/backend/src/lib/storage/file.ts @@ -1,4 +1,4 @@ -import { REPORTS_BUCKET } from './constants'; +import { REPORTS_BUCKET } from './constants.js'; export const isUUID = (uuid?: string): boolean => { return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid ?? ''); diff --git a/app/lib/storage/folders.ts b/apps/backend/src/lib/storage/folders.ts similarity index 100% rename from app/lib/storage/folders.ts rename to apps/backend/src/lib/storage/folders.ts diff --git a/app/lib/storage/format.ts b/apps/backend/src/lib/storage/format.ts similarity index 90% rename from app/lib/storage/format.ts rename to apps/backend/src/lib/storage/format.ts index 92658d75..52762952 100644 --- a/app/lib/storage/format.ts +++ b/apps/backend/src/lib/storage/format.ts @@ -1,4 +1,4 @@ -import { Result, Report } from './types'; +import type { Report, Result } from './types.js'; export const bytesToString = (bytes: number): string => { const units = ['B', 'KB', 'MB', 'GB', 'TB']; diff --git a/app/lib/storage/fs.ts b/apps/backend/src/lib/storage/fs.ts similarity index 59% rename from app/lib/storage/fs.ts rename to apps/backend/src/lib/storage/fs.ts index 051f9c13..439eef4c 100644 --- a/app/lib/storage/fs.ts +++ b/apps/backend/src/lib/storage/fs.ts @@ -1,13 +1,17 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; import { randomUUID } from 'node:crypto'; import { createWriteStream, type Dirent, type Stats } from 'node:fs'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import type { PassThrough } from 'node:stream'; import { pipeline } from 'node:stream/promises'; -import { PassThrough } from 'node:stream'; - +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; import getFolderSize from 'get-folder-size'; - -import { bytesToString } from './format'; +import { defaultConfig, isConfigValid, noConfigErr } from '../config.js'; +import { serveReportRoute } from '../constants.js'; +import { parse } from '../parser/index.js'; +import { generatePlaywrightReport } from '../pw.js'; +import { withError } from '../withError.js'; +import { processBatch } from './batch.js'; import { APP_CONFIG, DATA_FOLDER, @@ -17,28 +21,19 @@ import { REPORTS_PATH, RESULTS_FOLDER, TMP_FOLDER, -} from './constants'; -import { processBatch } from './batch'; -import { handlePagination } from './pagination'; -import { createDirectory } from './folders'; - -import { defaultConfig, isConfigValid, noConfigErr } from '@/app/lib/config'; -import { parse } from '@/app/lib/parser'; -import { generatePlaywrightReport } from '@/app/lib/pw'; -import { withError } from '@/app/lib/withError'; -import { serveReportRoute } from '@/app/lib/constants'; -import { - type Storage, - type Result, - type ServerDataInfo, - type ResultDetails, +} from './constants.js'; +import { createDirectory } from './folders.js'; +import { bytesToString } from './format.js'; +import type { ReadReportsOutput, - ReadReportsInput, - ReadResultsInput, - ReportMetadata, ReportHistory, -} from '@/app/lib/storage'; -import { SiteWhiteLabelConfig } from '@/app/types'; + ReportMetadata, + ReportPath, + Result, + ResultDetails, + ServerDataInfo, + Storage, +} from './types.js'; async function createDirectoriesIfMissing() { await createDirectory(RESULTS_FOLDER); @@ -77,15 +72,19 @@ export async function readFile(targetPath: string, contentType: string | null) { async function getResultsCount() { const files = await fs.readdir(RESULTS_FOLDER); + const zipFilesCount = files.filter((file) => file.endsWith('.zip')); - return Math.round(files.length / 2); + return zipFilesCount.length; } -export async function readResults(input?: ReadResultsInput) { +export async function readResults() { await createDirectoriesIfMissing(); const files = await fs.readdir(RESULTS_FOLDER); - const stats = await processBatch( + const stats = await processBatch< + string, + Stats & { filePath: string; size: string; sizeBytes: number } + >( {}, files.filter((file) => file.endsWith('.json')), 20, @@ -99,64 +98,29 @@ export async function readResults(input?: ReadResultsInput) { const size = bytesToString(sizeBytes); return Object.assign(stat, { filePath, size, sizeBytes }); - }, - ); - - const jsonFiles = stats.sort((a, b) => b.birthtimeMs - a.birthtimeMs); - - const fileContents: Result[] = await Promise.all( - jsonFiles.map(async (entry) => { - const content = await fs.readFile(entry.filePath, 'utf-8'); - - return { - size: entry.size, - sizeBytes: entry.sizeBytes, - ...JSON.parse(content), - }; - }), + } ); - let filteredResults = fileContents.filter((result) => (input?.project ? result.project === input.project : result)); - - // Filter by tags if provided - if (input?.tags && input.tags.length > 0) { - const notMetadataKeys = ['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project']; - - filteredResults = filteredResults.filter((result) => { - const resultTags = Object.entries(result) - .filter(([key]) => !notMetadataKeys.includes(key)) - .map(([key, value]) => `${key}: ${value}`); - - return input.tags!.some((selectedTag) => resultTags.includes(selectedTag)); - }); - } - - // Filter by search if provided - if (input?.search?.trim()) { - const searchTerm = input.search.toLowerCase().trim(); - - filteredResults = filteredResults.filter((result) => { - // Search in title, resultID, project, and all metadata fields - const searchableFields = [ - result.title, - result.resultID, - result.project, - ...Object.entries(result) - .filter(([key]) => !['resultID', 'title', 'createdAt', 'size', 'sizeBytes', 'project'].includes(key)) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const paginatedResults = handlePagination(filteredResults, input?.pagination); + const results = await processBatch< + Stats & { + filePath: string; + size: string; + sizeBytes: number; + }, + Result + >({}, stats, 10, async (entry) => { + const content = await fs.readFile(entry.filePath, 'utf-8'); + + return { + size: entry.size, + sizeBytes: entry.sizeBytes, + ...JSON.parse(content), + }; + }); return { - results: paginatedResults.map((result) => ({ - ...result, - })), - total: filteredResults.length, + results, + total: results.length, }; } @@ -166,7 +130,7 @@ function isMissingFileError(error?: Error | null) { async function readOrParseReportMetadata(id: string, projectName: string): Promise { const { result: metadataContent, error: metadataError } = await withError( - readFile(path.join(projectName, id, REPORT_METADATA_FILE), 'utf-8'), + readFile(path.join(projectName, id, REPORT_METADATA_FILE), 'utf-8') ); if (metadataError) console.error(`failed to read metadata for ${id}: ${metadataError.message}`); @@ -196,41 +160,75 @@ async function readOrParseReportMetadata(id: string, projectName: string): Promi return metadata; } -export async function readReports(input?: ReadReportsInput): Promise { +export async function readReport( + reportID: string, + reportPath: string +): Promise { await createDirectoriesIfMissing(); - const entries = await fs.readdir(REPORTS_FOLDER, { withFileTypes: true, recursive: true }); - const reportEntries = entries - .filter((entry) => !entry.isDirectory() && entry.name === 'index.html' && !(entry as any).path.endsWith('trace')) - .filter((entry) => (input?.ids ? input.ids.some((id) => (entry as any).path.includes(id)) : entry)) - .filter((entry) => (input?.project ? (entry as any).path.includes(input.project) : entry)); + console.log(`[fs] reading report ${reportID} metadata from path: ${reportPath}`); + + // Convert reportPath to relative path from REPORTS_FOLDER + const relativePath = path.relative(REPORTS_FOLDER, reportPath); + console.log(`[fs] reading report ${reportID} relative path: ${relativePath}`); + + const { result: metadataContent, error: metadataError } = await withError( + readFile(path.join(relativePath, REPORT_METADATA_FILE), 'utf-8') + ); + + if (metadataError) { + console.error(`[fs] failed to read metadata for ${reportID}: ${metadataError.message}`); + + return null; + } + + const metadata = metadataContent ? JSON.parse(metadataContent.toString()) : {}; + + return { + reportID, + project: metadata.project || '', + createdAt: new Date(metadata.createdAt), + size: metadata.size || '', + sizeBytes: metadata.sizeBytes || 0, + reportUrl: metadata.reportUrl || '', + ...metadata, + } as ReportHistory; +} + +export async function readReports(): Promise { + await createDirectoriesIfMissing(); + const entries = await fs.readdir(REPORTS_FOLDER, { + withFileTypes: true, + recursive: true, + }); + + const reportEntries = entries.filter( + (entry) => + !entry.isDirectory() && + entry.name === 'index.html' && + !('parentPath' in entry && entry.parentPath?.endsWith('trace')) + ); const stats = await processBatch( {}, reportEntries, 20, async (file) => { - const stat = await fs.stat((file as any).path); + const filePath = + 'parentPath' in file + ? file.parentPath + : path.join(REPORTS_FOLDER, (file as Dirent).name || ''); + const stat = await fs.stat(filePath); - return Object.assign(stat, { filePath: (file as any).path, createdAt: stat.birthtime }); - }, + return Object.assign(stat, { filePath, createdAt: stat.birthtime }); + } ); - const reportFiles = stats.sort((a, b) => b.birthtimeMs - a.birthtimeMs); - - const reportsWithProject = reportFiles - .map((file) => { - const id = path.basename(file.filePath); - const parentDir = path.basename(path.dirname(file.filePath)); - - const projectName = parentDir === REPORTS_PATH ? '' : parentDir; - - return Object.assign(file, { id, project: projectName }); - }) - .filter((report) => (input?.project ? input.project === report.project : report)); - - const allReports = await Promise.all( - reportsWithProject.map(async (file) => { + const reports = await processBatch( + {}, + stats, + 10, + async (file) => { const id = path.basename(file.filePath); const reportPath = path.dirname(file.filePath); const parentDir = path.basename(reportPath); @@ -249,37 +247,11 @@ export async function readReports(input?: ReadReportsInput): Promise { - // Search in title, reportID, project, and all metadata fields - const searchableFields = [ - report.title, - report.reportID, - report.project, - ...Object.entries(report) - .filter( - ([key]) => - !['reportID', 'title', 'createdAt', 'size', 'sizeBytes', 'project', 'reportUrl', 'stats'].includes(key), - ) - .map(([key, value]) => `${key}: ${value}`), - ].filter(Boolean); - - return searchableFields.some((field) => field?.toLowerCase().includes(searchTerm)); - }); - } - - const paginatedReports = handlePagination(filteredReports, input?.pagination); - - return { reports: paginatedReports as ReportHistory[], total: filteredReports.length }; + return { reports: reports, total: reports.length }; } export async function deleteResults(resultsIds: string[]) { @@ -292,15 +264,14 @@ export async function deleteResult(resultId: string) { await Promise.allSettled([fs.unlink(`${resultPath}.json`), fs.unlink(`${resultPath}.zip`)]); } -export async function deleteReports(reportsIds: string[]) { - const { reports } = await readReports({ ids: reportsIds }); - - const paths = reportsIds - .map((id) => reports.find((report) => report.reportID === id)) - .filter(Boolean) - .map((report) => (report?.project ? `${report.project}/${report.reportID}` : report?.reportID)); +export async function deleteReports(reports: ReportPath[]) { + const paths = reports.map((report) => + report.project ? `${report.project}/${report.reportID}` : report.reportID + ); - await Promise.allSettled(paths.map((path) => deleteReport(path!))); + await processBatch(undefined, paths, 10, async (path) => { + await deleteReport(path); + }); } export async function deleteReport(reportId: string) { @@ -325,7 +296,11 @@ export async function saveResult(filename: string, stream: PassThrough) { } } -export async function saveResultDetails(resultID: string, resultDetails: ResultDetails, size: number): Promise { +export async function saveResultDetails( + resultID: string, + resultDetails: ResultDetails, + size: number +): Promise { await createDirectoriesIfMissing(); const metaData = { @@ -340,7 +315,7 @@ export async function saveResultDetails(resultID: string, resultDetails: ResultD const { error: writeJsonError } = await withError( fs.writeFile(path.join(RESULTS_FOLDER, `${resultID}.json`), JSON.stringify(metaData, null, 2), { encoding: 'utf-8', - }), + }) ); if (writeJsonError) { @@ -367,7 +342,7 @@ export async function generateReport(resultsIds: string[], metadata?: ReportMeta await saveReportMetadata(generated.reportPath, info); - return reportId; + return { reportId, reportPath: generated.reportPath }; } finally { await fs.rm(tempFolder, { recursive: true, force: true }); } @@ -376,7 +351,7 @@ export async function generateReport(resultsIds: string[], metadata?: ReportMeta async function parseReportMetadata( reportID: string, reportPath: string, - metadata?: ReportMetadata, + metadata?: ReportMetadata ): Promise { const html = await fs.readFile(path.join(reportPath, 'index.html'), 'utf-8'); const info = await parse(html as string); @@ -387,7 +362,7 @@ async function parseReportMetadata( reportID, createdAt: new Date().toISOString(), }, - metadata ?? {}, + metadata ?? {} ); return content; @@ -419,7 +394,9 @@ async function readConfigFile() { return isValid ? { result: parsed, error: null } : { error: new Error('invalid config') }; } catch (e) { - return { error: new Error(`failed to parse config: ${e instanceof Error ? e.message : e}`) }; + return { + error: new Error(`failed to parse config: ${e instanceof Error ? e.message : e}`), + }; } } @@ -435,7 +412,11 @@ async function saveConfigFile(config: Partial) { const previousConfig = existingConfig ?? defaultConfig; const uploadConfig = { ...previousConfig, ...config }; - const { error } = await withError(fs.writeFile(APP_CONFIG, JSON.stringify(uploadConfig, null, 2), { flag: 'w+' })); + const { error } = await withError( + fs.writeFile(APP_CONFIG, JSON.stringify(uploadConfig, null, 2), { + flag: 'w+', + }) + ); return { result: uploadConfig, @@ -448,6 +429,7 @@ export const FS: Storage = { readFile, readResults, readReports, + readReport, deleteResults, deleteReports, saveResult, diff --git a/apps/backend/src/lib/storage/index.ts b/apps/backend/src/lib/storage/index.ts new file mode 100644 index 00000000..d1b5ef83 --- /dev/null +++ b/apps/backend/src/lib/storage/index.ts @@ -0,0 +1,7 @@ +export * from './types.js'; + +import { env } from '../../config/env.js'; +import { FS } from './fs.js'; +import { S3 } from './s3.js'; + +export const storage = env.DATA_STORAGE === 's3' ? S3.getInstance() : FS; diff --git a/apps/backend/src/lib/storage/pagination.ts b/apps/backend/src/lib/storage/pagination.ts new file mode 100644 index 00000000..24f65c14 --- /dev/null +++ b/apps/backend/src/lib/storage/pagination.ts @@ -0,0 +1,14 @@ +export interface Pagination { + limit: number; + offset: number; +} + +export const parseFromRequest = (searchParams: URLSearchParams): Pagination => { + const limitQuery = searchParams.get('limit') ?? ''; + const offsetQuery = searchParams.get('offset') ?? ''; + + const limit = limitQuery ? Number.parseInt(limitQuery, 10) : 20; + const offset = offsetQuery ? Number.parseInt(offsetQuery, 10) : 0; + + return { limit, offset }; +}; diff --git a/apps/backend/src/lib/storage/s3.ts b/apps/backend/src/lib/storage/s3.ts new file mode 100644 index 00000000..f6f66331 --- /dev/null +++ b/apps/backend/src/lib/storage/s3.ts @@ -0,0 +1,1229 @@ +import { randomUUID, type UUID } from 'node:crypto'; +import { createReadStream, createWriteStream } from 'node:fs'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import type { PassThrough, Readable } from 'node:stream'; + +import { + type _Object, + AbortMultipartUploadCommand, + CompleteMultipartUploadCommand, + CreateBucketCommand, + CreateMultipartUploadCommand, + DeleteObjectCommand, + GetObjectCommand, + HeadBucketCommand, + HeadObjectCommand, + ListObjectsV2Command, + PutObjectCommand, + S3Client, + UploadPartCommand, +} from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import { env } from '../../config/env.js'; +import { withError } from '../../lib/withError.js'; +import { defaultConfig, isConfigValid } from '../config.js'; +import { serveReportRoute } from '../constants.js'; +import { parse } from '../parser/index.js'; +import { generatePlaywrightReport } from '../pw.js'; +import { processBatch } from './batch.js'; +import { + APP_CONFIG_S3, + DATA_FOLDER, + DATA_PATH, + REPORT_METADATA_FILE, + REPORTS_BUCKET, + REPORTS_FOLDER, + REPORTS_PATH, + RESULTS_BUCKET, + TMP_FOLDER, +} from './constants.js'; +import { getFileReportID } from './file.js'; +import { bytesToString } from './format.js'; +import { + isReportHistory, + type ReadReportsOutput, + type ReadResultsOutput, + type Report, + type ReportHistory, + type ReportMetadata, + type ReportPath, + type Result, + type ResultDetails, + type ServerDataInfo, + type Storage, +} from './types.js'; + +const createClient = () => { + const endPoint = env.S3_ENDPOINT; + const accessKey = env.S3_ACCESS_KEY; + const secretKey = env.S3_SECRET_KEY; + const port = env.S3_PORT; + const region = env.S3_REGION; + + if (!endPoint) { + throw new Error('S3_ENDPOINT is required'); + } + + if (!accessKey) { + throw new Error('S3_ACCESS_KEY is required'); + } + + if (!secretKey) { + throw new Error('S3_SECRET_KEY is required'); + } + + console.log('[s3] creating client'); + + const protocol = 'https://'; + const endpointUrl = port ? `${protocol}${endPoint}:${port}` : `${protocol}${endPoint}`; + + const client = new S3Client({ + region: region || 'us-east-1', + endpoint: endpointUrl, + credentials: { + accessKeyId: accessKey, + secretAccessKey: secretKey, + }, + forcePathStyle: true, // required for S3-compatible services like Minio + }); + + return client; +}; + +export class S3 implements Storage { + private static instance: S3; + private readonly client: S3Client; + private readonly bucket: string; + private readonly batchSize: number; + + private constructor() { + this.client = createClient(); + this.bucket = env.S3_BUCKET; + this.batchSize = env.S3_BATCH_SIZE; + } + + public static getInstance() { + if (!S3.instance) { + S3.instance = new S3(); + } + + return S3.instance; + } + + private async ensureBucketExist() { + const { error } = await withError( + this.client.send(new HeadBucketCommand({ Bucket: this.bucket })) + ); + + if (!error) { + return; + } + + if (error.name === 'NotFound') { + console.log(`[s3] bucket ${this.bucket} does not exist, creating...`); + + const { error } = await withError( + this.client.send( + new CreateBucketCommand({ + Bucket: this.bucket, + }) + ) + ); + + if (error) { + console.error('[s3] failed to create bucket:', error); + } + } + + console.error('[s3] failed to check that bucket exist:', error); + } + + private async write( + dir: string, + files: { + name: string; + content: Readable | Buffer | string; + size?: number; + }[] + ) { + await this.ensureBucketExist(); + for (const file of files) { + const filePath = path.join(dir, file.name); + + console.log(`[s3] writing ${filePath}`); + + const content = typeof file.content === 'string' ? Buffer.from(file.content) : file.content; + + await this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: path.normalize(filePath), + Body: content, + }) + ); + } + } + + private async read(targetPath: string, contentType?: string | null) { + await this.ensureBucketExist(); + console.log(`[s3] read ${targetPath}`); + + const remotePath = targetPath.includes(REPORTS_BUCKET) + ? targetPath + : `${REPORTS_BUCKET}/${targetPath}`; + + console.log(`[s3] reading from remote path: ${remotePath}`); + + const { result: response, error } = await withError( + this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: remotePath, + }) + ) + ); + + if (error ?? !response?.Body) { + return { result: null, error }; + } + + const stream = response.Body as Readable; + + const readStream = new Promise((resolve, reject) => { + const chunks: Uint8Array[] = []; + + stream.on('data', (chunk: Uint8Array) => { + chunks.push(chunk); + }); + + stream.on('end', () => { + const fullContent = Buffer.concat(chunks); + + resolve(fullContent); + }); + + stream.on('error', (error) => { + console.error(`[s3] failed to read stream: ${error.message}`); + reject(error); + }); + }); + + const { result, error: readError } = await withError(readStream); + + return { + result: contentType === 'text/html' ? result?.toString('utf-8') : result, + error: error ?? readError ?? null, + }; + } + + async clear(...path: string[]) { + console.log(`[s3] clearing ${path}`); + // avoid using "removeObjects" as it is not supported by every S3-compatible provider + // for example, Google Cloud Storage. + await processBatch(this, path, this.batchSize, async (object) => { + await this.client.send( + new DeleteObjectCommand({ + Bucket: this.bucket, + Key: object, + }) + ); + }); + } + + async getFolderSize( + folderPath: string + ): Promise<{ size: number; resultCount: number; indexCount: number }> { + let resultCount = 0; + let indexCount = 0; + let totalSize = 0; + + let continuationToken: string | undefined; + + do { + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: folderPath, + ContinuationToken: continuationToken, + }) + ); + + for (const obj of response.Contents ?? []) { + if (obj.Key?.endsWith('.zip')) { + resultCount += 1; + } + + if (obj.Key?.endsWith('index.html') && !obj.Key.includes('/trace/index.html')) { + indexCount += 1; + } + + totalSize += obj?.Size ?? 0; + } + + continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined; + } while (continuationToken); + + return { size: totalSize, resultCount, indexCount }; + } + + async getServerDataInfo(): Promise { + await this.ensureBucketExist(); + console.log('[s3] getting server data'); + + const [results, reports] = await Promise.all([ + this.getFolderSize(RESULTS_BUCKET), + this.getFolderSize(REPORTS_BUCKET), + ]); + + const dataSize = results.size + reports.size; + + return { + dataFolderSizeinMB: bytesToString(dataSize), + numOfResults: results.resultCount, + resultsFolderSizeinMB: bytesToString(results.size), + numOfReports: reports.indexCount, + reportsFolderSizeinMB: bytesToString(reports.size), + }; + } + + async readFile(targetPath: string, contentType: string | null): Promise { + console.log(`[s3] reading ${targetPath} | ${contentType}`); + const { result, error } = await this.read(targetPath, contentType); + + if (error) { + console.error(`[s3] failed to read file ${targetPath}: ${error.message}`); + throw new Error(`[s3] failed to read file: ${error.message}`); + } + + return result!; + } + + async readResults(): Promise { + await this.ensureBucketExist(); + + console.log('[s3] reading results'); + + const jsonFiles: _Object[] = []; + const resultSizes = new Map(); + + let continuationToken: string | undefined; + + do { + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: RESULTS_BUCKET, + ContinuationToken: continuationToken, + }) + ); + + for (const file of response.Contents ?? []) { + if (!file?.Key) { + continue; + } + + if (file.Key.endsWith('.zip')) { + const resultID = path.basename(file.Key, '.zip'); + + resultSizes.set(resultID, file.Size ?? 0); + } + + if (file.Key.endsWith('.json')) { + jsonFiles.push(file); + } + } + + continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined; + } while (continuationToken); + + console.log(`[s3] found ${jsonFiles.length} json files`); + + if (!jsonFiles) { + return { + results: [], + total: 0, + }; + } + + const results = await processBatch<_Object, Result>( + this, + jsonFiles, + this.batchSize, + async (file) => { + console.log(`[s3.batch] reading result: ${JSON.stringify(file)}`); + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: file.Key!, + }) + ); + + const stream = response.Body as Readable; + let jsonString = ''; + + for await (const chunk of stream) { + jsonString += chunk.toString(); + } + + const parsed = JSON.parse(jsonString); + + return parsed; + } + ); + + return { + results: results.map((result) => { + const sizeBytes = resultSizes.get(result.resultID) ?? 0; + + return { + ...result, + sizeBytes, + size: result.size ?? bytesToString(sizeBytes), + }; + }) as Result[], + total: results.length, + }; + } + + async readReport(reportID: string, reportPath: string): Promise { + await this.ensureBucketExist(); + + console.log(`[s3] reading report ${reportID} metadata`); + + const relativePath = path.relative(reportPath, REPORTS_BUCKET); + + const objectKey = path.join(REPORTS_BUCKET, relativePath, REPORT_METADATA_FILE); + + console.log(`[s3] checking existence of result: ${objectKey}`); + const { error: headError } = await withError( + this.client.send( + new HeadObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + }) + ) + ); + + if (headError) { + throw new Error(`failed to check ${objectKey}: ${headError.message}`); + } + + console.log(`[s3] downloading metadata file: ${objectKey}`); + const localFilePath = path.join(TMP_FOLDER, reportID, REPORT_METADATA_FILE); + + const { error: downloadError } = await withError( + (async () => { + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + }) + ); + + const stream = response.Body as Readable; + const writeStream = createWriteStream(localFilePath); + + return new Promise((resolve, reject) => { + stream.pipe(writeStream); + writeStream.on('finish', resolve); + writeStream.on('error', reject); + stream.on('error', reject); + }); + })() + ); + + if (downloadError) { + console.error(`[s3] failed to download ${objectKey}: ${downloadError.message}`); + + throw new Error(`failed to download ${objectKey}: ${downloadError.message}`); + } + + try { + const content = await fs.readFile(localFilePath, 'utf-8'); + + const metadata = JSON.parse(content); + + return isReportHistory(metadata) ? metadata : null; + } catch (e) { + console.error(`[s3] failed to read or parse metadata file: ${(e as Error).message}`); + + return null; + } + } + + async readReports(): Promise { + await this.ensureBucketExist(); + + console.log(`[s3] reading reports from external storage`); + + const reports: Report[] = []; + const reportSizes = new Map(); + + let continuationToken: string | undefined; + + do { + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: REPORTS_BUCKET, + ContinuationToken: continuationToken, + }) + ); + + for (const file of response.Contents ?? []) { + if (!file?.Key) { + continue; + } + + const reportID = getFileReportID(file.Key); + + const newSize = (reportSizes.get(reportID) ?? 0) + (file.Size ?? 0); + + reportSizes.set(reportID, newSize); + + if (!file.Key.endsWith('index.html') || file.Key.includes('trace')) { + continue; + } + + const dir = path.dirname(file.Key); + const id = path.basename(dir); + const parentDir = path.basename(path.dirname(dir)); + + const projectName = parentDir === REPORTS_PATH ? '' : parentDir; + + const report = { + reportID: id, + project: projectName, + createdAt: file.LastModified ?? new Date(), + reportUrl: `${serveReportRoute}/${projectName ? encodeURIComponent(projectName) : ''}/${id}/index.html`, + size: '', + sizeBytes: 0, + }; + + reports.push(report); + } + + continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined; + } while (continuationToken); + + const withMetadata = await this.getReportsMetadata(reports as ReportHistory[]); + + return { + reports: withMetadata.map((report) => { + const sizeBytes = reportSizes.get(report.reportID) ?? 0; + + return { + ...report, + sizeBytes, + size: bytesToString(sizeBytes), + }; + }), + total: withMetadata.length, + }; + } + + async getReportsMetadata(reports: ReportHistory[]): Promise { + return await processBatch( + this, + reports, + this.batchSize, + async (report) => { + console.log(`[s3.batch] reading report ${report.reportID} metadata`); + + const { result: metadata, error: metadataError } = await withError( + this.readOrParseReportMetadata(report.reportID, report.project) + ); + + if (metadataError) { + console.error( + `[s3] failed to read or create metadata for ${report.reportID}: ${metadataError.message}` + ); + + return report; + } + + if (!metadata) { + return report; + } + + return Object.assign(metadata, report); + } + ); + } + + async readOrParseReportMetadata(id: string, projectName: string): Promise { + const { result: metadataContent, error: metadataError } = await withError( + this.readFile(path.join(REPORTS_BUCKET, projectName, id, REPORT_METADATA_FILE), 'utf-8') + ); + + if (metadataError) + console.error(`[s3] failed to read metadata for ${id}: ${metadataError.message}`); + + const metadata = + metadataContent && !metadataError ? JSON.parse(metadataContent.toString()) : {}; + + if (isReportHistory(metadata)) { + console.log(`metadata found for report ${id}`); + + return metadata; + } + + console.log(`metadata file not found for ${id}, creating new metadata`); + try { + const { result: htmlContent, error: htmlError } = await withError( + this.readFile(path.join(REPORTS_BUCKET, projectName, id, 'index.html'), 'utf-8') + ); + + if (htmlError) + console.error(`[s3] failed to read index.html for ${id}: ${htmlError.message}`); + + const created = await this.parseReportMetadata( + id, + path.join(REPORTS_FOLDER, projectName, id), + { + project: projectName, + reportID: id, + }, + htmlContent?.toString() + ); + + console.log(`metadata object created for ${id}: ${JSON.stringify(created)}`); + + await this.saveReportMetadata(id, path.join(REPORTS_FOLDER, projectName, id), created); + + Object.assign(metadata, created); + } catch (e) { + console.error(`failed to create metadata for ${id}: ${(e as Error).message}`); + } + + return metadata; + } + + async deleteResults(resultIDs: string[]): Promise { + const objects = resultIDs.flatMap((id) => [ + `${RESULTS_BUCKET}/${id}.json`, + `${RESULTS_BUCKET}/${id}.zip`, + ]); + + await withError(this.clear(...objects)); + } + + private async getReportObjects(reportsIDs: string[]): Promise { + const files: string[] = []; + + let continuationToken: string | undefined; + + do { + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: REPORTS_BUCKET, + ContinuationToken: continuationToken, + }) + ); + + for (const file of response.Contents ?? []) { + if (!file?.Key) { + continue; + } + + const reportID = path.basename(path.dirname(file.Key)); + + if (reportsIDs.includes(reportID)) { + files.push(file.Key); + } + } + + continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined; + } while (continuationToken); + + return files; + } + + async deleteReports(reports: ReportPath[]): Promise { + const ids = reports.map((r) => r.reportID); + const objects = await this.getReportObjects(ids); + + await withError(this.clear(...objects)); + } + + async generatePresignedUploadUrl(fileName: string) { + await this.ensureBucketExist(); + const objectKey = path.join(RESULTS_BUCKET, fileName); + const expiry = 30 * 60; // 30 minutes + + const command = new PutObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + }); + + return await getSignedUrl(this.client, command, { expiresIn: expiry }); + } + + async saveResult(filename: string, stream: PassThrough) { + await this.ensureBucketExist(); + + const chunkSizeMB = env.S3_MULTIPART_CHUNK_SIZE_MB; + const chunkSize = chunkSizeMB * 1024 * 1024; // bytes + + console.log(`[s3] starting multipart upload for ${filename} with chunk size ${chunkSizeMB}MB`); + + const remotePath = path.join(RESULTS_BUCKET, filename); + + const { UploadId: uploadID } = await this.client.send( + new CreateMultipartUploadCommand({ + Bucket: this.bucket, + Key: remotePath, + }) + ); + + if (!uploadID) { + throw new Error('[s3] failed to initiate multipart upload: no UploadId received'); + } + + const uploadedParts: { PartNumber: number; ETag: string }[] = []; + let partNumber = 1; + const chunks: Buffer[] = []; + let currentSize = 0; + + try { + for await (const chunk of stream) { + console.log( + `[s3] received chunk of size ${(chunk.length / (1024 * 1024)).toFixed(2)}MB for ${filename}` + ); + + chunks.push(chunk); + currentSize += chunk.length; + + while (currentSize >= chunkSize) { + const partData = Buffer.allocUnsafe(chunkSize); + let copied = 0; + + while (copied < chunkSize && chunks.length > 0) { + const currentChunk = chunks[0]; + const needed = chunkSize - copied; + const available = currentChunk.length; + + if (available <= needed) { + currentChunk.copy(partData, copied); + copied += available; + chunks.shift(); + } else { + currentChunk.copy(partData, copied, 0, needed); + copied += needed; + chunks[0] = currentChunk.subarray(needed); + } + } + + currentSize -= chunkSize; + + console.log( + `[s3] uploading part ${partNumber} (${(partData.length / (1024 * 1024)).toFixed(2)}MB) for ${filename}` + ); + console.log( + `[s3] buffer state: ${chunks.length} chunks, ${(currentSize / (1024 * 1024)).toFixed(2)}MB remaining` + ); + + stream.pause(); + + const uploadPartResult = await this.client.send( + new UploadPartCommand({ + Bucket: this.bucket, + Key: remotePath, + UploadId: uploadID, + PartNumber: partNumber, + Body: partData, + }) + ); + + // explicitly clear the part data to help GC + partData.fill(0); + + console.log(`[s3] uploaded part ${partNumber}, resume reading`); + stream.resume(); + + if (!uploadPartResult.ETag) { + throw new Error(`[s3] failed to upload part ${partNumber}: no ETag received`); + } + + uploadedParts.push({ + PartNumber: partNumber, + ETag: uploadPartResult.ETag, + }); + + partNumber++; + } + } + + if (currentSize > 0) { + console.log( + `[s3] uploading final part ${partNumber} [${bytesToString(currentSize)}] for ${filename}` + ); + + const finalPart = Buffer.allocUnsafe(currentSize); + let offset = 0; + + for (const chunk of chunks) { + chunk.copy(finalPart, offset); + offset += chunk.length; + } + + const uploadPartResult = await this.client.send( + new UploadPartCommand({ + Bucket: this.bucket, + Key: remotePath, + UploadId: uploadID, + PartNumber: partNumber, + Body: finalPart, + }) + ); + + // explicitly clear buffer references + chunks.length = 0; + finalPart.fill(0); + + if (!uploadPartResult.ETag) { + throw new Error(`[s3] failed to upload final part ${partNumber}: no ETag received`); + } + + uploadedParts.push({ + PartNumber: partNumber, + ETag: uploadPartResult.ETag, + }); + } + + console.log( + `[s3] completing multipart upload for ${filename} with ${uploadedParts.length} parts` + ); + + await this.client.send( + new CompleteMultipartUploadCommand({ + Bucket: this.bucket, + Key: remotePath, + UploadId: uploadID, + MultipartUpload: { + Parts: uploadedParts, + }, + }) + ); + + console.log(`[s3] multipart upload completed successfully for ${filename}`); + } catch (error) { + console.error(`[s3] multipart upload failed, aborting: ${(error as Error).message}`); + + await this.client.send( + new AbortMultipartUploadCommand({ + Bucket: this.bucket, + Key: remotePath, + UploadId: uploadID, + }) + ); + + throw error; + } + } + + async saveResultDetails( + resultID: string, + resultDetails: ResultDetails, + size: number + ): Promise { + const metaData = { + resultID, + createdAt: new Date().toISOString(), + project: resultDetails?.project ?? '', + ...resultDetails, + size: bytesToString(size), + sizeBytes: size, + }; + + await this.write(RESULTS_BUCKET, [ + { + name: `${resultID}.json`, + content: JSON.stringify(metaData), + }, + ]); + + return metaData as Result; + } + + private async uploadReport(reportId: string, reportPath: string, remotePath: string) { + console.log(`[s3] upload report: ${reportPath}`); + + const files = await fs.readdir(reportPath, { + recursive: true, + withFileTypes: true, + }); + + await processBatch(this, files, this.batchSize, async (file) => { + if (!file.isFile()) { + return; + } + + console.log(`[s3] uploading file: ${JSON.stringify(file)}`); + + const nestedPath = (file as any).path.split(reportId).pop(); + const s3Path = path.join(remotePath, nestedPath ?? '', file.name); + + console.log(`[s3] uploading to ${s3Path}`); + + const { error } = await withError( + this.uploadFileWithRetry(s3Path, path.join((file as any).path, file.name)) + ); + + if (error) { + console.error(`[s3] failed to upload report: ${error.message}`); + throw new Error(`[s3] failed to upload report: ${error.message}`); + } + }); + } + + private async uploadFileWithRetry( + remotePath: string, + filePath: string, + attempt = 1 + ): Promise { + if (attempt > 3) { + throw new Error(`[s3] failed to upload file after ${attempt} attempts: ${filePath}`); + } + + const fileStream = createReadStream(filePath); + + const { error } = await withError( + this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: remotePath, + Body: fileStream, + }) + ) + ); + + if (error) { + console.error(`[s3] failed to upload file: ${error.message}`); + console.log(`[s3] will retry in 3s...`); + + return await this.uploadFileWithRetry(remotePath, filePath, attempt + 1); + } + } + + private async clearTempFolders(id?: string) { + const withReportPathMaybe = id ? ` for report ${id}` : ''; + + console.log(`[s3] clear temp folders${withReportPathMaybe}`); + + await withError(fs.rm(path.join(TMP_FOLDER, id ?? ''), { recursive: true, force: true })); + await withError(fs.rm(REPORTS_FOLDER, { recursive: true, force: true })); + } + + async generateReport( + resultsIds: string[], + metadata?: ReportMetadata + ): Promise<{ reportId: UUID; reportPath: string }> { + console.log(`[s3] generate report from results: ${JSON.stringify(resultsIds)}`); + console.log(`[s3] create temp folders`); + const { error: mkdirReportsError } = await withError( + fs.mkdir(REPORTS_FOLDER, { recursive: true }) + ); + + if (mkdirReportsError) { + console.error(`[s3] failed to create reports folder: ${mkdirReportsError.message}`); + } + + const reportId = randomUUID(); + const tempFolder = path.join(TMP_FOLDER, reportId); + + const { error: mkdirTempError } = await withError(fs.mkdir(tempFolder, { recursive: true })); + + if (mkdirTempError) { + console.error(`[s3] failed to create temporary folder: ${mkdirTempError.message}`); + } + + console.log(`[s3] start processing...`); + + for (const resultId of resultsIds) { + const fileName = `${resultId}.zip`; + const objectKey = path.join(RESULTS_BUCKET, fileName); + + console.log(`[s3] checking existence of result: ${objectKey}`); + const { error: headError } = await withError( + this.client.send( + new HeadObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + }) + ) + ); + + if (headError) { + console.error(`[s3] result ${resultId} not found, skipping: ${headError.message}`); + throw new Error(`failed to check ${objectKey}: ${headError.message}`); + } + + console.log(`[s3] downloading result: ${objectKey}`); + const localFilePath = path.join(tempFolder, fileName); + + const { error: downloadError } = await withError( + (async () => { + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: objectKey, + }) + ); + + const stream = response.Body as Readable; + const writeStream = createWriteStream(localFilePath); + + return new Promise((resolve, reject) => { + stream.pipe(writeStream); + writeStream.on('finish', resolve); + writeStream.on('error', reject); + stream.on('error', reject); + }); + })() + ); + + if (downloadError) { + console.error(`[s3] failed to download ${objectKey}: ${downloadError.message}`); + + throw new Error(`failed to download ${objectKey}: ${downloadError.message}`); + } + + console.log(`[s3] downloaded: ${objectKey} to ${localFilePath}`); + } + + const { reportPath } = await generatePlaywrightReport(reportId, metadata!); + + console.log(`[s3] report generated: ${reportId} | ${reportPath}`); + + const { result: info, error: parseReportMetadataError } = await withError( + this.parseReportMetadata(reportId, reportPath, metadata) + ); + + if (parseReportMetadataError) console.error(parseReportMetadataError.message); + + const remotePath = path.join(REPORTS_BUCKET, metadata?.project ?? '', reportId); + + const { error: uploadError } = await withError( + this.uploadReport(reportId, reportPath, remotePath) + ); + + if (uploadError) { + console.error(`[s3] failed to upload report: ${uploadError.message}`); + } else { + const { error } = await withError( + this.saveReportMetadata(reportId, reportPath, info ?? metadata ?? {}) + ); + + if (error) console.error(`[s3] failed to save report metadata: ${error.message}`); + } + + await this.clearTempFolders(reportId); + + return { reportId, reportPath }; + } + + private async saveReportMetadata(reportId: string, reportPath: string, metadata: ReportMetadata) { + console.log(`[s3] report uploaded: ${reportId}, uploading metadata to ${reportPath}`); + const { error: metadataError } = await withError( + this.write(path.join(REPORTS_BUCKET, metadata.project ?? '', reportId), [ + { + name: REPORT_METADATA_FILE, + content: JSON.stringify(metadata), + }, + ]) + ); + + if (metadataError) + console.error(`[s3] failed to upload report metadata: ${metadataError.message}`); + } + + private async parseReportMetadata( + reportId: string, + reportPath: string, + metadata?: Record, + htmlContent?: string // to pass file content if stored on s3 + ): Promise { + console.log(`[s3] creating report metadata for ${reportId} and ${reportPath}`); + const html = htmlContent ?? (await fs.readFile(path.join(reportPath, 'index.html'), 'utf-8')); + + const info = await parse(html as string); + + const content = Object.assign(info, metadata, { + reportId, + createdAt: new Date().toISOString(), + }); + + return content; + } + + async readConfigFile(): Promise<{ + result?: SiteWhiteLabelConfig; + error: Error | null; + }> { + await this.ensureBucketExist(); + console.log(`[s3] checking config file`); + + const { result: response, error } = await withError( + this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: APP_CONFIG_S3, + }) + ) + ); + + if (error) { + console.error(`[s3] failed to read config file: ${error.message}`); + + return { error }; + } + + const stream = response?.Body as Readable; + let existingConfig = ''; + + for await (const chunk of stream ?? []) { + existingConfig += chunk.toString(); + } + + try { + const parsed = JSON.parse(existingConfig); + + const isValid = isConfigValid(parsed); + + if (!isValid) { + return { error: new Error('invalid config') }; + } + + // ensure custom images available locally in data folder + for (const image of [ + { path: parsed.faviconPath, default: defaultConfig.faviconPath }, + { path: parsed.logoPath, default: defaultConfig.logoPath }, + ]) { + if (!image) continue; + if (image.path === image.default) continue; + + const localPath = path.join(DATA_FOLDER, image.path); + const { error: accessError } = await withError(fs.access(localPath)); + + if (accessError) { + const remotePath = path.join(DATA_PATH, image.path); + + console.log(`[s3] downloading config image: ${remotePath} to ${localPath}`); + + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: remotePath, + }) + ); + + const stream = response.Body as Readable; + const writeStream = createWriteStream(localPath); + + await new Promise((resolve, reject) => { + stream.pipe(writeStream); + writeStream.on('finish', resolve); + writeStream.on('error', reject); + stream.on('error', reject); + }); + } + } + + return { result: parsed, error: null }; + } catch (e) { + return { + error: new Error(`failed to parse config: ${e instanceof Error ? e.message : e}`), + }; + } + } + + async saveConfigFile(config: Partial) { + console.log(`[s3] writing config file`); + + const { result: existingConfig, error: readExistingConfigError } = await this.readConfigFile(); + + if (readExistingConfigError) { + console.error(`[s3] failed to read existing config file: ${readExistingConfigError.message}`); + } + + const { error: clearExistingConfigError } = await withError(this.clear(APP_CONFIG_S3)); + + if (clearExistingConfigError) { + console.error( + `[s3] failed to clear existing config file: ${clearExistingConfigError.message}` + ); + } + + const uploadConfig = { + ...(existingConfig ?? {}), + ...config, + } as SiteWhiteLabelConfig; + + const isDefaultImage = (key: keyof SiteWhiteLabelConfig) => + config[key] && config[key] === defaultConfig[key]; + + const shouldBeUploaded = async (key: keyof SiteWhiteLabelConfig) => { + if (!config[key]) return false; + if (isDefaultImage(key)) return false; + + const imagePath = key === 'logoPath' ? uploadConfig.logoPath : uploadConfig.faviconPath; + + const { result } = await withError( + this.client.send( + new HeadObjectCommand({ + Bucket: this.bucket, + Key: path.join(DATA_PATH, imagePath), + }) + ) + ); + + if (!result) { + return true; + } + + return false; + }; + + if (await shouldBeUploaded('logoPath')) { + await this.uploadConfigImage(uploadConfig.logoPath); + } + + if (await shouldBeUploaded('faviconPath')) { + await this.uploadConfigImage(uploadConfig.faviconPath); + } + + const { error } = await withError( + this.write(DATA_PATH, [ + { + name: 'config.json', + content: JSON.stringify(uploadConfig, null, 2), + }, + ]) + ); + + if (error) console.error(`[s3] failed to write config file: ${error.message}`); + + return { result: uploadConfig, error }; + } + + private async uploadConfigImage(imagePath: string): Promise { + console.log(`[s3] uploading config image: ${imagePath}`); + + const localPath = path.join(DATA_FOLDER, imagePath); + const remotePath = path.join(DATA_PATH, imagePath); + + const { error } = await withError(this.uploadFileWithRetry(remotePath, localPath)); + + if (error) { + console.error(`[s3] failed to upload config image: ${error.message}`); + + return error; + } + + return null; + } +} diff --git a/app/lib/storage/types.ts b/apps/backend/src/lib/storage/types.ts similarity index 56% rename from app/lib/storage/types.ts rename to apps/backend/src/lib/storage/types.ts index b0e8079a..772f9e81 100644 --- a/app/lib/storage/types.ts +++ b/apps/backend/src/lib/storage/types.ts @@ -1,26 +1,40 @@ -import { PassThrough } from 'node:stream'; - -import { type Pagination } from './pagination'; - -import { type SiteWhiteLabelConfig, type UUID } from '@/app/types'; -import { type ReportInfo, type ReportTest } from '@/app/lib/parser/types'; +import type { PassThrough } from 'node:stream'; +import type { SiteWhiteLabelConfig, UUID } from '@playwright-reports/shared'; +import type { ReportInfo, ReportTest } from '../parser/types.js'; +import type { Pagination } from './pagination.js'; export interface Storage { getServerDataInfo: () => Promise; readFile: (targetPath: string, contentType: string | null) => Promise; - readResults: (input?: ReadResultsInput) => Promise; - readReports: (input?: ReadReportsInput) => Promise; + readResults: () => Promise; + readReports: () => Promise; + readReport: (reportID: string, reportPath: string) => Promise; deleteResults: (resultIDs: string[]) => Promise; - deleteReports: (reportIDs: string[]) => Promise; + deleteReports: (reports: ReportPath[]) => Promise; saveResult: (filename: string, stream: PassThrough) => Promise; - saveResultDetails: (resultID: string, resultDetails: ResultDetails, size: number) => Promise; - generateReport: (resultsIds: string[], metadata?: ReportMetadata) => Promise; - readConfigFile: () => Promise<{ result?: SiteWhiteLabelConfig; error: Error | null }>; + saveResultDetails: ( + resultID: string, + resultDetails: ResultDetails, + size: number + ) => Promise; + generateReport: ( + resultsIds: string[], + metadata?: ReportMetadata + ) => Promise<{ reportId: UUID; reportPath: string }>; + readConfigFile: () => Promise<{ + result?: SiteWhiteLabelConfig; + error: Error | null; + }>; saveConfigFile: ( - config: Partial, + config: Partial ) => Promise<{ result: SiteWhiteLabelConfig; error: Error | null }>; } +export interface ReportPath { + reportID: string; + project: string; +} + export interface ReadResultsInput { pagination?: Pagination; project?: string; @@ -77,12 +91,17 @@ export type Report = { export type ReportHistory = Report & ReportInfo; -export const isReportHistory = (report: Report | ReportHistory | undefined): report is ReportHistory => - !!report && typeof report === 'object' && 'stats' in report; +export const isReportHistory = ( + report: Report | ReportHistory | undefined +): report is ReportHistory => !!report && typeof report === 'object' && 'stats' in report; export type TestHistory = Report & ReportTest; -export type ReportMetadata = Partial<{ title: string; project: string; playwrightVersion?: string }> & +export type ReportMetadata = Partial<{ + title: string; + project: string; + playwrightVersion?: string; +}> & Record; export interface ServerDataInfo { diff --git a/app/lib/time.ts b/apps/backend/src/lib/time.ts similarity index 73% rename from app/lib/time.ts rename to apps/backend/src/lib/time.ts index 175e9be8..fb5c5c68 100644 --- a/app/lib/time.ts +++ b/apps/backend/src/lib/time.ts @@ -18,3 +18,10 @@ export const parseMilliseconds = (ms: number) => { return `${leftMs}ms`; }; + +export const getTimestamp = (date?: Date | string) => { + if (!date) return 0; + if (typeof date === 'string') return new Date(date).getTime(); + + return date.getTime(); +}; diff --git a/app/lib/transformers.ts b/apps/backend/src/lib/transformers.ts similarity index 81% rename from app/lib/transformers.ts rename to apps/backend/src/lib/transformers.ts index fb3b4770..59b48d1b 100644 --- a/app/lib/transformers.ts +++ b/apps/backend/src/lib/transformers.ts @@ -1,5 +1,5 @@ -import { ReportFile, ReportTest, ReportTestFilters } from '@/app/lib/parser'; -import { ReportHistory } from '@/app/lib/storage/types'; +import type { ReportFile, ReportTest, ReportTestFilters } from './parser/index.js'; +import type { ReportHistory } from './storage/types.js'; const isTestMatchingFilters = (test: ReportTest, filters?: ReportTestFilters): boolean => { if (!filters) { @@ -13,7 +13,7 @@ const isTestMatchingFilters = (test: ReportTest, filters?: ReportTestFilters): b export const filterReportHistory = ( report: ReportHistory, - filters?: ReportTestFilters, + filters?: ReportTestFilters ): ReportHistory & { testCount: number; totalTestCount: number } => { const filtered = structuredClone(report); const counter = { @@ -47,7 +47,12 @@ export const filterReportHistory = ( }; }; -export const pluralize = (count: number, singular: string, plural: string, locale: string = 'en-US'): string => { +export const pluralize = ( + count: number, + singular: string, + plural: string, + locale: string = 'en-US' +): string => { const pluralRules = new Intl.PluralRules(locale); const rule = pluralRules.select(count); diff --git a/app/lib/url.ts b/apps/backend/src/lib/url.ts similarity index 81% rename from app/lib/url.ts rename to apps/backend/src/lib/url.ts index d35d7dbd..d8b00e88 100644 --- a/app/lib/url.ts +++ b/apps/backend/src/lib/url.ts @@ -1,4 +1,4 @@ -import { env } from '../config/env'; +import { env } from '../config/env.js'; export const withBase = (p = '') => { const base = (env.API_BASE_PATH || '').replace(/\/+$/, ''); diff --git a/apps/backend/src/lib/validation/index.ts b/apps/backend/src/lib/validation/index.ts new file mode 100644 index 00000000..d4e40b55 --- /dev/null +++ b/apps/backend/src/lib/validation/index.ts @@ -0,0 +1,82 @@ +import type { FastifyReply, FastifyRequest } from 'fastify'; +import type { z } from 'zod'; + +export class ValidationError extends Error { + constructor( + message: string, + public details?: any + ) { + super(message); + this.name = 'ValidationError'; + } +} + +export function validateSchema(schema: T, data: unknown): z.infer { + const result = schema.safeParse(data); + if (!result.success) { + console.log('Validation error data:', data); + console.log('Validation error:', result.error); + const errorMessages = result.error.issues.map((err: any) => ({ + field: err.path.join('.'), + message: err.message, + })); + throw new ValidationError('Validation failed', errorMessages); + } + return result.data; +} + +export function validateQuery(schema: T) { + return async (request: FastifyRequest, reply: FastifyReply) => { + try { + request.query = validateSchema(schema, request.query); + } catch (error) { + if (error instanceof ValidationError) { + return reply.status(400).send({ + error: 'Invalid query parameters', + details: error.details, + }); + } + return reply.status(500).send({ error: 'Internal server error' }); + } + }; +} + +export function validateBody(schema: T) { + return async (request: FastifyRequest, reply: FastifyReply) => { + try { + request.body = validateSchema(schema, request.body); + } catch (error) { + if (error instanceof ValidationError) { + return reply.status(400).send({ + error: 'Invalid request body', + details: error.details, + }); + } + return reply.status(500).send({ error: 'Internal server error' }); + } + }; +} + +export function validateParams(schema: T) { + return async (request: FastifyRequest, reply: FastifyReply) => { + try { + request.params = validateSchema(schema, request.params); + } catch (error) { + if (error instanceof ValidationError) { + return reply.status(400).send({ + error: 'Invalid URL parameters', + details: error.details, + }); + } + return reply.status(500).send({ error: 'Internal server error' }); + } + }; +} + +export function createJsonSchema(zodSchema: z.ZodType) { + return zodTypeToJsonSchema(zodSchema); +} + +function zodTypeToJsonSchema(_zodType: any): any { + return { type: 'any' }; +} diff --git a/app/lib/withError.ts b/apps/backend/src/lib/withError.ts similarity index 65% rename from app/lib/withError.ts rename to apps/backend/src/lib/withError.ts index c9b37597..58e16a77 100644 --- a/app/lib/withError.ts +++ b/apps/backend/src/lib/withError.ts @@ -1,4 +1,6 @@ -export async function withError(promise: Promise): Promise<{ result: T | null; error: Error | null }> { +export async function withError( + promise: Promise +): Promise<{ result: T | null; error: Error | null }> { try { const result = await promise; diff --git a/apps/backend/src/routes/auth.ts b/apps/backend/src/routes/auth.ts new file mode 100644 index 00000000..fe0ee29e --- /dev/null +++ b/apps/backend/src/routes/auth.ts @@ -0,0 +1,236 @@ +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import jwt from 'jsonwebtoken'; +import { env } from '../config/env.js'; + +const useAuth = !!env.API_TOKEN; + +// strictly recommended to specify via env var +// Use a stable default secret when AUTH_SECRET is not set to avoid JWT decryption errors +// This is only acceptable when auth is disabled (no API_TOKEN) +const secret = env.AUTH_SECRET ?? 'default-secret-for-non-auth-mode'; + +const expirationHours = env.UI_AUTH_EXPIRE_HOURS + ? Number.parseInt(env.UI_AUTH_EXPIRE_HOURS, 10) + : 2; +const expirationSeconds = expirationHours * 60 * 60; + +interface AuthUser { + apiToken: string; + jwtToken: string; +} + +interface AuthRequest extends Omit { + user?: AuthUser; +} + +const createAuthTokens = (apiToken: string): AuthUser => { + const jwtToken = jwt.sign({ authorized: true }, secret); + return { apiToken, jwtToken }; +}; + +const createNoAuthTokens = (): AuthUser => { + const token = jwt.sign({ authorized: true }, secret); + return { apiToken: token, jwtToken: token }; +}; + +const verifyToken = (token: string): { authorized: boolean } | null => { + try { + return jwt.verify(token, secret) as { authorized: boolean }; + } catch (_error) { + return null; + } +}; + +export const authenticate = async (request: AuthRequest, reply: FastifyReply) => { + if (!useAuth) { + request.user = createNoAuthTokens(); + return; + } + + const authHeader = request.headers.authorization; + const cookieToken = request.cookies.token; + + let token = null; + + if (authHeader?.startsWith('Bearer ')) { + token = authHeader.substring(7); + } else if (cookieToken) { + token = cookieToken; + } + + if (!token) { + return reply.status(401).send({ error: 'Unauthorized: No token provided' }); + } + + const decoded = verifyToken(token); + if (!decoded) { + return reply.status(401).send({ error: 'Unauthorized: Invalid token' }); + } + + request.user = { + apiToken: '', // should not store the original API token in JWT + jwtToken: token, + }; +}; + +export const getSession = async (request: AuthRequest, reply: FastifyReply) => { + if (!useAuth) { + const noAuthUser = createNoAuthTokens(); + return { + user: { + apiToken: noAuthUser.apiToken, + jwtToken: noAuthUser.jwtToken, + }, + expires: new Date(Date.now() + expirationSeconds * 1000).toISOString(), + }; + } + + try { + await authenticate(request, reply); + + if (request.user) { + return { + user: { + apiToken: request.user.apiToken, + jwtToken: request.user.jwtToken, + }, + expires: new Date(Date.now() + expirationSeconds * 1000).toISOString(), + }; + } + } catch (error) { + return reply.status(401).send({ error: 'Unauthorized' }); + } +}; + +export async function registerAuthRoutes(fastify: FastifyInstance) { + fastify.post('/api/auth/signin', async (request, reply) => { + try { + const { apiToken } = request.body as { apiToken?: string }; + + if (!useAuth) { + const noAuthUser = createNoAuthTokens(); + + // Set token in cookie + reply.setCookie('token', noAuthUser.jwtToken, { + path: '/', + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: expirationSeconds, + }); + + return { + user: { + apiToken: noAuthUser.apiToken, + jwtToken: noAuthUser.jwtToken, + }, + success: true, + }; + } + + if (!apiToken || apiToken !== env.API_TOKEN) { + return reply.status(401).send({ + error: 'Invalid API token', + success: false, + }); + } + + const authUser = createAuthTokens(apiToken); + + reply.setCookie('token', authUser.jwtToken, { + path: '/', + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: expirationSeconds, + }); + + return { + user: { + apiToken: authUser.apiToken, + jwtToken: authUser.jwtToken, + }, + success: true, + }; + } catch (error) { + fastify.log.error({ error }, 'Sign in error'); + return reply.status(500).send({ + error: 'Internal server error', + success: false, + }); + } + }); + + fastify.post('/api/auth/signout', (_, reply) => { + try { + reply.clearCookie('token', { path: '/' }); + + return { + success: true, + message: 'Signed out successfully', + }; + } catch (error) { + fastify.log.error({ error }, 'Sign out error'); + return reply.status(500).send({ + error: 'Internal server error', + success: false, + }); + } + }); + + fastify.get('/api/auth/session', async (request, reply) => { + try { + const sessionData = await getSession(request as AuthRequest, reply); + return sessionData; + } catch (error) { + return reply.status(401).send({ error: 'Unauthorized' }); + } + }); + + fastify.get('/api/auth/csrf', async (_request, _reply) => { + const csrfToken = Buffer.from(Date.now().toString()).toString('base64'); + + return { + csrfToken, + }; + }); + + fastify.get('/api/auth/providers', async (_request, _reply) => { + return { + credentials: { + id: 'credentials', + name: 'API Token', + type: 'credentials', + signinUrl: '/api/auth/signin/credentials', + callbackUrl: '/api/auth/callback/credentials', + }, + }; + }); + + fastify.all('/api/auth/*', async (request, reply) => { + // Handle various next-auth endpoints for compatibility + const path = (request.params as { '*': string })['*'] || ''; + + switch (path) { + case 'signin': + return reply.send({ + providers: [ + { + id: 'credentials', + name: 'API Token', + type: 'credentials', + }, + ], + }); + + case 'session': + return await getSession(request as AuthRequest, reply); + + default: + return reply.status(404).send({ error: 'Not found' }); + } + }); +} + +export type { AuthRequest, AuthUser }; +export { useAuth, secret, expirationSeconds }; diff --git a/apps/backend/src/routes/config.ts b/apps/backend/src/routes/config.ts new file mode 100644 index 00000000..3175f0f4 --- /dev/null +++ b/apps/backend/src/routes/config.ts @@ -0,0 +1,253 @@ +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { env } from '../config/env.js'; +import { cronService } from '../lib/service/cron.js'; +import { getDatabaseStats } from '../lib/service/db/index.js'; +import { service } from '../lib/service/index.js'; +import { JiraService } from '../lib/service/jira.js'; +import { DATA_FOLDER } from '../lib/storage/constants.js'; +import { withError } from '../lib/withError.js'; +import { type AuthRequest, authenticate } from './auth.js'; + +interface MultipartFile { + fieldname: string; + filename?: string; + toBuffer(): Promise; +} + +interface ConfigFormData { + title?: string; + logoPath?: string; + faviconPath?: string; + reporterPaths?: string; + headerLinks?: string; + jiraBaseUrl?: string; + jiraEmail?: string; + jiraApiToken?: string; + jiraProjectKey?: string; + resultExpireDays?: string; + resultExpireCronSchedule?: string; + reportExpireDays?: string; + reportExpireCronSchedule?: string; +} + +export async function registerConfigRoutes(fastify: FastifyInstance) { + fastify.get('/api/config', async (_request, reply) => { + const { result: config, error } = await withError(service.getConfig()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + const envInfo = { + authRequired: !!env.API_TOKEN, + database: getDatabaseStats(), + dataStorage: env.DATA_STORAGE, + s3Endpoint: env.S3_ENDPOINT, + s3Bucket: env.S3_BUCKET, + }; + + return { ...config, ...envInfo }; + }); + + fastify.patch('/api/config', async (request: FastifyRequest, reply: FastifyReply) => { + try { + const authResult = await authenticate(request as AuthRequest, reply); + if (authResult) return authResult; + + const data = await request.file({ + limits: { files: 2 }, + }); + + if (!data) { + return reply.status(400).send({ error: 'No data received' }); + } + + let logoFile: MultipartFile | null = null; + let faviconFile: MultipartFile | null = null; + const formData: ConfigFormData = {}; + + for await (const part of data.file) { + if (part.type === 'file') { + const filePart = part as MultipartFile; + if (part.fieldname === 'logo') { + logoFile = filePart; + } else if (part.fieldname === 'favicon') { + faviconFile = filePart; + } + } else if (part.type === 'field') { + const fieldName = part.fieldname as keyof ConfigFormData; + formData[fieldName] = part.value as string; + } + } + + const config = await service.getConfig(); + + if (!config) { + return reply.status(500).send({ error: 'failed to get config' }); + } + + if (logoFile) { + const { error: logoError } = await withError( + writeFile(join(DATA_FOLDER, logoFile.filename!), Buffer.from(await logoFile.toBuffer())) + ); + + if (logoError) { + return reply.status(500).send({ error: `failed to save logo: ${logoError?.message}` }); + } + config.logoPath = `/${logoFile.filename}`; + } + + if (faviconFile) { + const { error: faviconError } = await withError( + writeFile( + join(DATA_FOLDER, faviconFile.filename!), + Buffer.from(await faviconFile.toBuffer()) + ) + ); + + if (faviconError) { + return reply.status(500).send({ + error: `failed to save favicon: ${faviconError?.message}`, + }); + } + config.faviconPath = `/${faviconFile.filename}`; + } + + if (formData.title !== undefined) { + config.title = formData.title; + } + + if (formData.logoPath !== undefined && !logoFile) { + config.logoPath = formData.logoPath; + } + + if (formData.faviconPath !== undefined && !faviconFile) { + config.faviconPath = formData.faviconPath; + } + + if (formData.reporterPaths !== undefined) { + try { + config.reporterPaths = JSON.parse(formData.reporterPaths); + } catch { + config.reporterPaths = [formData.reporterPaths]; + } + } + + if (formData.headerLinks !== undefined) { + try { + const parsedHeaderLinks = JSON.parse(formData.headerLinks); + if (parsedHeaderLinks) config.headerLinks = parsedHeaderLinks; + } catch (error) { + return reply.status(400).send({ + error: `failed to parse header links: ${error instanceof Error ? error.message : 'Invalid JSON'}`, + }); + } + } + + if (!config.jira) { + config.jira = {}; + } + + if (formData.jiraBaseUrl !== undefined) config.jira.baseUrl = formData.jiraBaseUrl; + if (formData.jiraEmail !== undefined) config.jira.email = formData.jiraEmail; + if (formData.jiraApiToken !== undefined) config.jira.apiToken = formData.jiraApiToken; + if (formData.jiraProjectKey !== undefined) config.jira.projectKey = formData.jiraProjectKey; + + if ( + formData.jiraBaseUrl || + formData.jiraEmail || + formData.jiraApiToken || + formData.jiraProjectKey + ) { + JiraService.resetInstance(); + } + + if (!config.cron) { + config.cron = {}; + } + + if (formData.resultExpireDays !== undefined) { + config.cron.resultExpireDays = Number.parseInt(formData.resultExpireDays, 10); + } + if (formData.resultExpireCronSchedule !== undefined) { + config.cron.resultExpireCronSchedule = formData.resultExpireCronSchedule; + } + if (formData.reportExpireDays !== undefined) { + config.cron.reportExpireDays = Number.parseInt(formData.reportExpireDays, 10); + } + if (formData.reportExpireCronSchedule !== undefined) { + config.cron.reportExpireCronSchedule = formData.reportExpireCronSchedule; + } + + const { error: saveConfigError } = await withError(service.updateConfig(config)); + + if (saveConfigError) { + return reply.status(500).send({ + error: `failed to save config: ${saveConfigError.message}`, + }); + } + + if ( + config.cron?.resultExpireDays || + config.cron?.resultExpireCronSchedule || + config.cron?.reportExpireDays || + config.cron?.reportExpireCronSchedule + ) { + await cronService.restart(); + } + + return reply.send({ message: 'config saved' }); + } catch (error) { + fastify.log.error({ error }, 'Config update error'); + return reply.status(400).send({ + error: `config update failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + } + }); + + fastify.post('/api/config', async (request, reply) => { + const { result, error } = await withError( + service.updateConfig(request.body as Partial) + ); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return result; + }); + + fastify.get('/api/info', async (_request, reply) => { + const { result: info, error } = await withError(service.getServerInfo()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return info; + }); + + fastify.post( + '/api/cache/refresh', + { + schema: { + response: { + 200: { type: 'object' }, + 400: { type: 'object', properties: { error: { type: 'string' } } }, + }, + }, + }, + async (_request, reply) => { + const { result, error } = await withError(service.refreshCache()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return result; + } + ); +} diff --git a/apps/backend/src/routes/index.ts b/apps/backend/src/routes/index.ts new file mode 100644 index 00000000..52d4f00f --- /dev/null +++ b/apps/backend/src/routes/index.ts @@ -0,0 +1,16 @@ +import type { FastifyInstance } from 'fastify'; +import { registerAuthRoutes } from './auth.js'; +import { registerConfigRoutes } from './config.js'; +import { registerJiraRoutes } from './jira.js'; +import { registerReportRoutes } from './reports.js'; +import { registerResultRoutes } from './results.js'; +import { registerServeRoutes } from './serve.js'; + +export async function registerApiRoutes(fastify: FastifyInstance) { + await registerAuthRoutes(fastify); + await registerReportRoutes(fastify); + await registerResultRoutes(fastify); + await registerConfigRoutes(fastify); + await registerServeRoutes(fastify); + await registerJiraRoutes(fastify); +} diff --git a/apps/backend/src/routes/jira.ts b/apps/backend/src/routes/jira.ts new file mode 100644 index 00000000..1dc810a7 --- /dev/null +++ b/apps/backend/src/routes/jira.ts @@ -0,0 +1,160 @@ +import type { FastifyInstance } from 'fastify'; +import { service } from '../lib/service/index.js'; +import { JiraService } from '../lib/service/jira.js'; +import { withError } from '../lib/withError.js'; +import { type AuthRequest, authenticate } from './auth.js'; + +interface CreateTicketRequest { + summary: string; + description: string; + issueType: string; + projectKey: string; + testId: string; + testTitle: string; + testOutcome: string; + testLocation: { + file: string; + line: number; + column: number; + }; + reportId: string; + testAttachments?: Array<{ + name: string; + path: string; + contentType: string; + }>; +} + +export async function registerJiraRoutes(fastify: FastifyInstance) { + fastify.get('/api/jira/config', async (_request, reply) => { + try { + const config = await service.getConfig(); + const jiraConfig = config.jira; + + const isConfigured = !!(jiraConfig?.baseUrl && jiraConfig?.email && jiraConfig?.apiToken); + + if (!isConfigured) { + return reply.send({ + configured: false, + message: 'Jira is not configured. Please configure Jira settings in the admin panel.', + config: jiraConfig || {}, + }); + } + + const jiraService = JiraService.getInstance(jiraConfig); + + let issueTypes = []; + + if (jiraConfig?.projectKey) { + try { + const project = await jiraService.getProject(jiraConfig.projectKey); + + issueTypes = project.issueTypes || []; + } catch (error) { + fastify.log.warn( + { error }, + `Could not fetch project-specific issue types for ${jiraConfig.projectKey}` + ); + } + } + + return reply.send({ + configured: true, + baseUrl: jiraConfig.baseUrl, + defaultProjectKey: jiraConfig.projectKey, + issueTypes: issueTypes.map((type: any) => ({ + id: type.id, + name: type.name, + description: type.description, + })), + }); + } catch (error) { + return reply.status(500).send({ + configured: false, + error: `Failed to connect to Jira: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + } + }); + + fastify.post('/api/jira/create-ticket', async (request, reply) => { + const authResult = await authenticate(request as AuthRequest, reply); + if (authResult) return authResult; + + const { result: data, error: parseError } = await withError(Promise.resolve(request.body)); + + if (parseError) { + return reply.status(400).send({ error: parseError.message }); + } + + if (!data) { + return reply.status(400).send({ error: 'Request data is missing' }); + } + + const ticketData = data as CreateTicketRequest; + + try { + const report = await service.getReport(ticketData.reportId); + const projectPath = report.project ? `${report.project}/` : ''; + + ticketData.testAttachments = ticketData.testAttachments?.map((att) => ({ + ...att, + path: `${projectPath}${ticketData.reportId}/${att.path}`, + })); + } catch (error) { + fastify.log.error({ error }, `Failed to get report ${ticketData.reportId}`); + } + + try { + if (!ticketData.summary || !ticketData.projectKey) { + return reply.status(400).send({ + error: 'Summary and project key are required', + }); + } + + const config = await service.getConfig(); + const jiraService = JiraService.getInstance(config.jira); + + const jiraResponse = await jiraService.createIssue( + ticketData.summary, + ticketData.description, + ticketData.issueType, + ticketData.projectKey, + { + testId: ticketData.testId, + testTitle: ticketData.testTitle, + testOutcome: ticketData.testOutcome, + testLocation: ticketData.testLocation, + }, + ticketData.testAttachments + ); + + return reply.status(201).send({ + success: true, + issueKey: jiraResponse.key, + issueId: jiraResponse.id, + issueUrl: jiraResponse.self, + message: 'Jira ticket created successfully', + data: { + ...ticketData, + issueKey: jiraResponse.key, + issueId: jiraResponse.id, + issueUrl: jiraResponse.self, + created: new Date().toISOString(), + }, + }); + } catch (error) { + fastify.log.error({ error }, 'Failed to create Jira ticket'); + + if (error instanceof Error && error.message.includes('Jira configuration is incomplete')) { + return reply.status(500).send({ + error: + 'Jira is not configured. Please set up JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.', + }); + } + + return reply.status(500).send({ + error: `Failed to create Jira ticket: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + } + }); +} diff --git a/apps/backend/src/routes/reports.ts b/apps/backend/src/routes/reports.ts new file mode 100644 index 00000000..0758fbbd --- /dev/null +++ b/apps/backend/src/routes/reports.ts @@ -0,0 +1,163 @@ +import type { FastifyInstance } from 'fastify'; +import { + DeleteReportsRequestSchema, + GenerateReportRequestSchema, + GetReportParamsSchema, + ListReportsQuerySchema, +} from '../lib/schemas/index.js'; +import { service } from '../lib/service/index.js'; +import { parseFromRequest } from '../lib/storage/pagination.js'; +import { validateSchema } from '../lib/validation/index.js'; +import { withError } from '../lib/withError.js'; + +export async function registerReportRoutes(fastify: FastifyInstance) { + fastify.get('/api/report/list', async (request, reply) => { + try { + const query = validateSchema(ListReportsQuerySchema, request.query); + const params = new URLSearchParams(); + if (query.limit !== undefined) { + params.append('limit', query.limit.toString()); + } + if (query.offset !== undefined) { + params.append('offset', query.offset.toString()); + } + const pagination = parseFromRequest(params); + + const { result: reports, error } = await withError( + service.getReports({ + pagination, + project: query.project, + search: query.search, + }) + ); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return reports; + } catch (error) { + console.error('[routes] list reports error:', error); + return reply.status(500).send({ error: 'Internal server error' }); + } + }); + + fastify.get('/api/report/:id', async (request, reply) => { + try { + const params = validateSchema(GetReportParamsSchema, request.params); + const { result: report, error } = await withError(service.getReport(params.id)); + + if (error) { + return reply.status(404).send({ error: error.message }); + } + + return report; + } catch (error) { + console.error('[routes] get report error:', error); + return reply.status(500).send({ error: 'Internal server error' }); + } + }); + + fastify.get('/api/report/projects', async (_request, reply) => { + try { + const { result: projects, error } = await withError(service.getReportsProjects()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return projects; + } catch (error) { + console.error('[routes] get projects error:', error); + return reply.status(500).send({ error: 'Internal server error' }); + } + }); + + fastify.post('/api/report/generate', async (request, reply) => { + try { + const body = (request.body as { resultsIds?: unknown; [key: string]: unknown }) || {}; + + if (!body.resultsIds || !Array.isArray(body.resultsIds)) { + return reply.status(400).send({ error: 'resultsIds array is required' }); + } + + if (body.resultsIds.length === 0) { + return reply.status(400).send({ error: 'At least one result ID must be provided' }); + } + + const validatedBody = validateSchema(GenerateReportRequestSchema, body); + console.log(`[routes] generate report request:`, validatedBody); + + const metadata: Record = { + ...(validatedBody.project && { project: validatedBody.project }), + ...(validatedBody.playwrightVersion && { + playwrightVersion: validatedBody.playwrightVersion, + }), + ...(validatedBody.title && { title: validatedBody.title }), + ...Object.fromEntries( + Object.entries(validatedBody) + .filter( + ([key]) => + !['resultsIds', 'project', 'playwrightVersion', 'title'].includes(key) && + typeof validatedBody[key as keyof typeof validatedBody] === 'string' + ) + .map(([key, value]) => [key, String(value)]) + ), + }; + + const { result, error } = await withError( + service.generateReport(validatedBody.resultsIds, metadata) + ); + + if (error) { + console.error(`[routes] generate report error:`, error.message); + + if (error instanceof Error && error.message.includes('ENOENT: no such file or directory')) { + return reply.status(404).send({ + error: `ResultID not found: ${error.message}`, + }); + } + + return reply.status(400).send({ error: error.message }); + } + + console.log(`[routes] generate report success:`, result); + return result; + } catch (error) { + console.error('[routes] generate report validation error:', error); + return reply.status(400).send({ error: 'Invalid request format' }); + } + }); + + fastify.delete('/api/report/delete', async (request, reply) => { + try { + const body = (request.body as { reportsIds?: unknown }) || { reportsIds: [] }; + + if (!body.reportsIds || !Array.isArray(body.reportsIds)) { + return reply.status(400).send({ error: 'reportsIds array is required' }); + } + + if (body.reportsIds.length === 0) { + return reply.status(400).send({ error: 'At least one report ID must be provided' }); + } + + const validatedBody = validateSchema(DeleteReportsRequestSchema, body); + console.log(`[routes] delete reports:`, validatedBody.reportsIds); + + const { error } = await withError(service.deleteReports(validatedBody.reportsIds)); + + if (error) { + console.error(`[routes] delete reports error:`, error); + return reply.status(404).send({ error: error.message }); + } + + return reply.status(200).send({ + message: 'Reports deleted successfully', + reportsIds: validatedBody.reportsIds, + }); + } catch (error) { + console.error('[routes] delete reports validation error:', error); + return reply.status(400).send({ error: 'Invalid request format' }); + } + }); +} diff --git a/apps/backend/src/routes/results.ts b/apps/backend/src/routes/results.ts new file mode 100644 index 00000000..db86af59 --- /dev/null +++ b/apps/backend/src/routes/results.ts @@ -0,0 +1,291 @@ +import { randomUUID } from 'node:crypto'; +import { PassThrough } from 'node:stream'; +import type { FastifyInstance } from 'fastify'; +import { DeleteResultsRequestSchema, ListResultsQuerySchema } from '../lib/schemas/index.js'; +import { service } from '../lib/service/index.js'; +import { DEFAULT_STREAM_CHUNK_SIZE } from '../lib/storage/constants.js'; +import { parseFromRequest } from '../lib/storage/pagination.js'; +import { validateSchema } from '../lib/validation/index.js'; +import { withError } from '../lib/withError.js'; +import { type AuthRequest, authenticate } from './auth.js'; + +export async function registerResultRoutes(fastify: FastifyInstance) { + fastify.get('/api/result/list', async (request, reply) => { + try { + const query = validateSchema(ListResultsQuerySchema, request.query); + const params = new URLSearchParams(); + if (query.limit !== undefined) { + params.append('limit', query.limit.toString()); + } + if (query.offset !== undefined) { + params.append('offset', query.offset.toString()); + } + const pagination = parseFromRequest(params); + const tags = query.tags ? query.tags.split(',').filter(Boolean) : []; + + const { result, error } = await withError( + service.getResults({ + pagination, + project: query.project, + tags, + search: query.search, + }) + ); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return result; + } catch (error) { + console.error('[routes] list results error:', error); + return reply.status(500).send({ error: 'Internal server error' }); + } + }); + + fastify.get('/api/result/projects', async (_, reply) => { + const { result: projects, error } = await withError(service.getResultsProjects()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return projects; + }); + + fastify.get('/api/result/tags', async (_, reply) => { + const { result: tags, error } = await withError(service.getResultsTags()); + + if (error) { + return reply.status(400).send({ error: error.message }); + } + + return tags; + }); + + fastify.delete( + '/api/result/delete', + { + config: { + rawBody: true, + }, + }, + async (request, reply) => { + try { + const body = (request.body as { resultsIds?: unknown }) || { resultsIds: [] }; + + if (!body.resultsIds || !Array.isArray(body.resultsIds)) { + return reply.status(400).send({ error: 'resultsIds array is required' }); + } + + if (body.resultsIds.length === 0) { + return reply.status(400).send({ error: 'At least one result ID must be provided' }); + } + + const validatedBody = validateSchema(DeleteResultsRequestSchema, body); + console.log(`[routes] delete results:`, validatedBody.resultsIds); + + const { error } = await withError(service.deleteResults(validatedBody.resultsIds)); + + if (error) { + console.error(`[routes] delete results error:`, error); + return reply.status(404).send({ error: error.message }); + } + + console.log(`[routes] delete results - deletion successful`); + return reply.status(200).send({ + message: 'Results files deleted successfully', + resultsIds: validatedBody.resultsIds, + }); + } catch (error) { + console.error('[routes] delete results validation error:', error); + return reply.status(400).send({ error: 'Invalid request format' }); + } + } + ); + + fastify.put('/api/result/upload', async (request, reply) => { + const authResult = await authenticate(request as AuthRequest, reply); + if (authResult) return authResult; + + const resultID = randomUUID(); + const fileName = `${resultID}.zip`; + + const query = request.query as Record; + const contentLength = query['fileContentLength'] || ''; + + if (contentLength || Number.parseInt(contentLength, 10)) { + console.log( + `[upload] fileContentLength query parameter is provided for result ${resultID}, using presigned URL flow` + ); + } + + // if there is fileContentLength query parameter we can use presigned URL for direct upload + const presignedUrl = contentLength ? await service.getPresignedUrl(fileName) : ''; + + const resultDetails: Record = {}; + let fileSize = 0; + + const filePassThrough = new PassThrough({ + highWaterMark: DEFAULT_STREAM_CHUNK_SIZE, + }); + + try { + const data = await request.file({ + limits: { files: 1, fileSize: 100 * 1024 * 1024 }, + }); + + if (!data) { + return reply.status(400).send({ error: 'upload result failed: No file received' }); + } + + for (const [key, prop] of Object.entries(data.fields)) { + if (key === 'file') continue; + + if (prop && typeof prop === 'object' && 'value' in prop) { + resultDetails[key] = String(prop.value); + } else { + resultDetails[key] = typeof prop === 'string' ? prop : String(prop); + } + } + + if (data.file && !Array.isArray(data.file)) { + const saveResultPromise = service.saveResult( + fileName, + filePassThrough, + presignedUrl, + contentLength + ); + + let isPaused = false; + + data.file.on('data', (chunk: Buffer) => { + fileSize += chunk.length; + + const canContinue = filePassThrough.write(chunk); + + if (!canContinue && !isPaused) { + isPaused = true; + data.file?.pause(); + } + }); + + filePassThrough.on('drain', () => { + if (isPaused) { + isPaused = false; + data.file?.resume(); + } + }); + + data.file.on('end', () => { + console.log('[upload] file ended'); + filePassThrough.end(); + }); + + data.file.on('error', (error) => { + console.error('[upload] file error:', error); + filePassThrough.destroy(); + throw error; + }); + + data.file.pipe(filePassThrough); + await saveResultPromise; + + if (contentLength) { + const expected = Number.parseInt(contentLength, 10); + if (Number.isFinite(expected) && expected > 0 && fileSize !== expected) { + return reply.status(400).send({ + error: `Size mismatch: received ${fileSize} bytes, expected ${expected} bytes`, + }); + } + } + } + + const { result: uploadResult, error: uploadResultDetailsError } = await withError( + service.saveResultDetails(resultID, resultDetails, fileSize) + ); + + if (uploadResultDetailsError) { + return reply.status(400).send({ + error: `upload result details failed: ${uploadResultDetailsError.message}`, + }); + } + + let generatedReport = null; + + if ( + resultDetails.shardCurrent && + resultDetails.shardTotal && + resultDetails.triggerReportGeneration === 'true' + ) { + const { result: results, error: resultsError } = await withError( + service.getResults({ + testRun: resultDetails.testRun, + }) + ); + + if (resultsError) { + return reply.status(500).send({ + error: `failed to generate report: ${resultsError.message}`, + }); + } + + const testRunResults = results?.results.filter( + (result) => + result.testRun === resultDetails.testRun && + (resultDetails.project ? result.project === resultDetails.project : true) + ); + + console.log( + `found ${testRunResults?.length} results for the test run ${resultDetails.testRun}` + ); + + if (testRunResults?.length === Number.parseInt(resultDetails.shardTotal)) { + const ids = testRunResults.map((result) => result.resultID); + + console.log('triggerReportGeneration for', resultDetails.testRun, ids); + const { result, error } = await withError( + service.generateReport(ids, { + project: resultDetails.project, + testRun: resultDetails.testRun, + playwrightVersion: resultDetails.playwrightVersion, + }) + ); + + if (error) { + return reply.status(500).send({ error: `failed to generate report: ${error.message}` }); + } + + generatedReport = result; + } + } + + return reply.status(200).send({ + message: 'Success', + data: { + ...uploadResult, + generatedReport, + }, + }); + } catch (error) { + console.error('[upload] error:', error); + + if (!filePassThrough.destroyed) { + filePassThrough.destroy(); + } + + const { error: deleteError } = await withError(service.deleteResults([resultID])); + if (deleteError) { + console.error(`[upload] cleanup failed for result ${resultID}:`, deleteError); + reply.status(400).send({ + error: `upload result failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + return; + } + + return reply.status(400).send({ + error: `upload result failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + } + }); +} diff --git a/apps/backend/src/routes/serve.ts b/apps/backend/src/routes/serve.ts new file mode 100644 index 00000000..33f7059a --- /dev/null +++ b/apps/backend/src/routes/serve.ts @@ -0,0 +1,83 @@ +import { access, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { FastifyInstance } from 'fastify'; +import mime from 'mime'; +import { env } from '../config/env.js'; +import { DATA_FOLDER } from '../lib/storage/constants.js'; +import { storage } from '../lib/storage/index.js'; +import { withError } from '../lib/withError.js'; +import { type AuthRequest, authenticate } from './auth.js'; + +export async function registerServeRoutes(fastify: FastifyInstance) { + fastify.get('/api/serve/*', async (request, reply) => { + try { + const filePath = (request.params as { '*': string })['*'] || ''; + const targetPath = decodeURI(filePath); + + const authRequired = !!env.API_TOKEN; + + if (authRequired) { + const authResult = await authenticate(request as AuthRequest, reply); + if (authResult) return authResult; + } + + const contentType = mime.getType(targetPath.split('/').pop() || ''); + + if (!contentType && !targetPath.includes('.')) { + return reply.code(404).send({ error: 'Not Found' }); + } + + const { result: content, error } = await withError( + storage.readFile(targetPath, contentType || null) + ); + + if (error || !content) { + return reply.code(404).send({ + error: `Could not read file: ${error?.message || 'File not found'}`, + }); + } + + const headers: Record = { + 'Content-Type': contentType ?? 'application/octet-stream', + }; + + if ((request as AuthRequest).user?.apiToken) { + headers['X-API-Token'] = (request as AuthRequest).user!.apiToken; + } + + return reply.code(200).headers(headers).send(content); + } catch (error) { + fastify.log.error({ error }, 'File serving error'); + return reply.code(500).send({ error: 'Internal server error' }); + } + }); + + fastify.get('/api/static/*', async (request, reply) => { + try { + const filePath = (request.params as { '*': string })['*'] || ''; + const targetPath = decodeURI(filePath); + + const contentType = mime.getType(targetPath.split('/').pop() || ''); + + if (!contentType && !targetPath.includes('.')) { + return reply.code(404).send({ error: 'Not Found' }); + } + + const imageDataPath = join(DATA_FOLDER, targetPath); + const imagePublicPath = join(process.cwd(), 'public', targetPath); + + const { error: dataAccessError } = await withError(access(imageDataPath)); + const imagePath = dataAccessError ? imagePublicPath : imageDataPath; + + const imageBuffer = await readFile(imagePath); + + return reply + .code(200) + .header('Content-Type', contentType || 'image/*') + .send(imageBuffer); + } catch (error) { + fastify.log.error({ error }, 'Static file serving error'); + return reply.code(404).send({ error: 'File not found' }); + } + }); +} diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json new file mode 100644 index 00000000..5c2d3e32 --- /dev/null +++ b/apps/backend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "paths": { + "@/*": ["./src/*"], + "@playwright-reports/shared": ["./node_modules/@playwright-reports/shared/src"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/backend/tsconfig.node.json b/apps/backend/tsconfig.node.json new file mode 100644 index 00000000..eca66688 --- /dev/null +++ b/apps/backend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example new file mode 100644 index 00000000..25c035c7 --- /dev/null +++ b/apps/frontend/.env.example @@ -0,0 +1,8 @@ +# Frontend environment variables for Vite +# All variables must be prefixed with VITE_ to be available in the browser + +# API base path - useful when frontend is served from a subdirectory +VITE_API_BASE_PATH= + +# API URL - fallback for production when backend is on different origin +VITE_API_URL=http://localhost:3001 \ No newline at end of file diff --git a/apps/frontend/index.html b/apps/frontend/index.html new file mode 100644 index 00000000..b673d13e --- /dev/null +++ b/apps/frontend/index.html @@ -0,0 +1,21 @@ + + + + + + + Playwright Reports Server + + + + + + + +
+ + + diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json new file mode 100644 index 00000000..29c00bfb --- /dev/null +++ b/apps/frontend/package-lock.json @@ -0,0 +1,8151 @@ +{ + "name": "playwright-reports-server-frontend", + "version": "5.7.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playwright-reports-server-frontend", + "version": "5.7.4", + "dependencies": { + "@heroui/link": "2.2.12", + "@heroui/navbar": "2.2.13", + "@heroui/react": "2.7.4", + "@heroui/switch": "2.2.13", + "@heroui/system": "2.4.11", + "@heroui/theme": "2.4.11", + "@react-aria/selection": "^3.12.1", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.15.0", + "@react-aria/visually-hidden": "^3.8.28", + "@tanstack/react-query": "^5.59.15", + "@tanstack/react-query-devtools": "^5.59.15", + "clsx": "^2.1.1", + "framer-motion": "^11.11.8", + "next-themes": "^0.4.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "recharts": "^2.13.0", + "sonner": "^1.5.0", + "tailwind-merge": "^3.0.2" + }, + "devDependencies": { + "@types/react": "18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "10.4.20", + "postcss": "8.4.47", + "tailwind-variants": "0.2.1", + "tailwindcss": "3.4.13", + "typescript": "5.2.2", + "vite": "^6.0.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.2", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@heroui/accordion": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/accordion/-/accordion-2.2.12.tgz", + "integrity": "sha512-ged/4UkDgro/6yL8vN7tdgMYcClq1ttA3dpF8xfqhoThS3P8yA9I/rX4Bf6WB1V/QOPnDpat6N9nJj5mKTwUGA==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/divider": "2.2.10", + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-accordion": "2.2.7", + "@react-aria/button": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-stately/tree": "3.8.7", + "@react-types/accordion": "3.0.0-alpha.26", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/accordion/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/alert": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/alert/-/alert-2.2.15.tgz", + "integrity": "sha512-Zvwilzf5iYiL6aKz5FV2yzjImBi3jE0NYXLjLR7h1zczUSewKaUG0aqYCo8mw3U9SWX7Nj2DP1mboLyNCD0mRA==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.15", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@react-aria/utils": "3.27.0", + "@react-stately/utils": "3.10.5" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/alert/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/aria-utils": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/aria-utils/-/aria-utils-2.2.12.tgz", + "integrity": "sha512-vTcIo0NE6htNY6vSltmmwgQH5Lye+6/dBWcFG18qmBptiwFjJ6EPxoHzBNtUkRY2wJFYNNKOfPViQBe9qQepwA==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/system": "2.4.11", + "@react-aria/utils": "3.27.0", + "@react-stately/collections": "3.12.1", + "@react-stately/overlays": "3.6.13", + "@react-types/overlays": "3.8.12", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/aria-utils/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/autocomplete": { + "version": "2.3.16", + "resolved": "https://registry.npmjs.org/@heroui/autocomplete/-/autocomplete-2.3.16.tgz", + "integrity": "sha512-brOUl5cuIaWgftOffFGi1IG4LijKRCvMGgWL8MNylI1xrsBfl1VmeRuSn8CpJDKllZICseePOTerLKITl/50Kg==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/button": "2.2.15", + "@heroui/form": "2.1.14", + "@heroui/input": "2.4.15", + "@heroui/listbox": "2.3.14", + "@heroui/popover": "2.3.15", + "@heroui/react-utils": "2.1.8", + "@heroui/scroll-shadow": "2.3.10", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/spinner": "2.2.12", + "@heroui/use-aria-button": "2.2.9", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/combobox": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/combobox": "3.10.2", + "@react-types/combobox": "3.13.2", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/autocomplete/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/autocomplete/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/avatar": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/avatar/-/avatar-2.2.11.tgz", + "integrity": "sha512-exXfXsma3X33YoU3C7n96zctGRtOn8yIsIF97quTekSThWn4bcCCo2Otu2hLbGZVR5QikyPWlG6MnXTohzqXxw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-image": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/avatar/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/badge": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/badge/-/badge-2.2.10.tgz", + "integrity": "sha512-ZY+7zvgHUW7Ye4Epdd4GnbmJgf59pGjxGML6Jm+R+GvjdqUSpneRW+QUNzwL8paSwXbGTP48nnQh9fMs+PKyRQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/breadcrumbs": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/breadcrumbs/-/breadcrumbs-2.2.11.tgz", + "integrity": "sha512-DfrJhUT+HyiFh/F5j6CI7yxVuwd+F2sfJOC3EJrrbsrUS1q5ZXapkX1e4hkzAJ4SG31cb+fM3Np+8ga/Vdq+sg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@react-aria/breadcrumbs": "3.5.20", + "@react-aria/focus": "3.19.1", + "@react-aria/utils": "3.27.0", + "@react-types/breadcrumbs": "3.7.10", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/breadcrumbs/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/button": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/button/-/button-2.2.15.tgz", + "integrity": "sha512-ixKxTiwHBMz0vVur4vKGBplOwJwZx0gg1fb6Rzs79CivXu/pgZQkGj9SyC3smkJ+PNRMHxKnMg5haMTZCiINpw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/ripple": "2.2.12", + "@heroui/shared-utils": "2.1.7", + "@heroui/spinner": "2.2.12", + "@heroui/use-aria-button": "2.2.9", + "@react-aria/button": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-types/button": "3.10.2", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/button/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/calendar": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/calendar/-/calendar-2.2.15.tgz", + "integrity": "sha512-B8qfMwp6ijc7rY6SrN4XxBHGYCJodmAs6wF51R8H7Cmn+2YW4nCbjl+thf8RdE8oA/f130rvew43cdJPjHcy7A==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.15", + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-button": "2.2.9", + "@internationalized/date": "3.7.0", + "@react-aria/calendar": "3.7.0", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/calendar": "3.7.0", + "@react-stately/utils": "3.10.5", + "@react-types/button": "3.10.2", + "@react-types/calendar": "3.6.0", + "@react-types/shared": "3.27.0", + "@types/lodash.debounce": "^4.0.7", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/calendar/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/calendar/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/card": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/card/-/card-2.2.14.tgz", + "integrity": "sha512-s8ZaCCSChkGehvLGYX3VEDX70PzOwQAG6ikXaEeHD3poO7wMZzdxQw73RtCGf/XAlJCpWgJ18xTdHGfm3aQESQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/ripple": "2.2.12", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-button": "2.2.9", + "@react-aria/button": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/card/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/checkbox": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@heroui/checkbox/-/checkbox-2.3.14.tgz", + "integrity": "sha512-Yn1h6IhYlhxEsR235BUgHzh6yAR6JPTzdr97PSKf/xJox0D4HK6uyKj0YCmPwIgYCxGfd1NaqXRTm3FM4RRkFg==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-callback-ref": "2.1.6", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/checkbox": "3.15.1", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/checkbox": "3.6.11", + "@react-stately/toggle": "3.8.1", + "@react-types/checkbox": "3.9.1", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/checkbox/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/checkbox/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/chip": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/chip/-/chip-2.2.11.tgz", + "integrity": "sha512-FFSK+bwnbZFXA2zk/Y49VKvwZlgMCEk6L0du/4knyFc1vtoI2+vr+W8uRM82xXAXptCkrZlXKm6vlDKaKTDH2Q==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-types/checkbox": "3.9.1" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/chip/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/code": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/code/-/code-2.2.11.tgz", + "integrity": "sha512-nNz8wxpm5epPWptVca1sWKpqYvCEGXVWzuuTLcXLX4lmmZyE/Dj0G3rlf1Sq8skxzQpdfPy52mvUog+/ONrJfQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/system-rsc": "2.3.10" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-input": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@heroui/date-input/-/date-input-2.3.14.tgz", + "integrity": "sha512-p7qgD5BAMuJGQcXMUbRdMG0tXRPZtrLiF/sMysVHUYsxLTAdi99j+7a1l905GhLHSsHuqdWoGr/YfAxVzytftg==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@internationalized/date": "3.7.0", + "@react-aria/datepicker": "3.13.0", + "@react-aria/i18n": "3.12.5", + "@react-aria/utils": "3.27.0", + "@react-stately/datepicker": "3.12.0", + "@react-types/datepicker": "3.10.0", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-input/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/date-picker": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@heroui/date-picker/-/date-picker-2.3.15.tgz", + "integrity": "sha512-MLzeMB1//WClwKF6IAuEcHChiobd4SVa0jr5Y7ScYzj4Gw9qvDAd/cfIZUrhQ0nXHZUvwmfcLEi2YdVqMuFWZQ==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/button": "2.2.15", + "@heroui/calendar": "2.2.15", + "@heroui/date-input": "2.3.14", + "@heroui/form": "2.1.14", + "@heroui/popover": "2.3.15", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@internationalized/date": "3.7.0", + "@react-aria/datepicker": "3.13.0", + "@react-aria/i18n": "3.12.5", + "@react-aria/utils": "3.27.0", + "@react-stately/datepicker": "3.12.0", + "@react-stately/overlays": "3.6.13", + "@react-stately/utils": "3.10.5", + "@react-types/datepicker": "3.10.0", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-picker/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/divider": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/divider/-/divider-2.2.10.tgz", + "integrity": "sha512-L3aEQ+ClUafzFJCmgCPjWL3ZDVHqSPRjLRE0XHpqC2xAP5dU0Hq44lhzCYHHKHHk1ZP/f1WDeas65C8lcHLZpA==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/system-rsc": "2.3.10", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/dom-animation": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/dom-animation/-/dom-animation-2.1.6.tgz", + "integrity": "sha512-l4xh+y02lmoJVdLR0cjpsa7LjLIvVQCX+w+S2KW6tOoPKmHlyW/8r7h6SqPB4Ua1NZGmRHtlYmw+mw47yqyTjw==", + "license": "MIT", + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1" + } + }, + "node_modules/@heroui/drawer": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/drawer/-/drawer-2.2.12.tgz", + "integrity": "sha512-27OEnfn82S2b143C372yKx8DBUct5htbk6KFHlH/enZeMsppOLnSCXsX7QpZas8c6B5k03otvUyL+YQ29/JdTw==", + "license": "MIT", + "dependencies": { + "@heroui/framer-utils": "2.1.11", + "@heroui/modal": "2.2.12", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/dropdown": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@heroui/dropdown/-/dropdown-2.3.15.tgz", + "integrity": "sha512-MoOFQJy8MBL5Qiclirt5LouEYi9+4H4wlrAdCYdsGGrdoivS2UT/jllc2oEwRLYyRLgnkKyqh5UqnxHWNrnX6g==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/menu": "2.2.14", + "@heroui/popover": "2.3.15", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/menu": "3.17.0", + "@react-aria/utils": "3.27.0", + "@react-stately/menu": "3.9.1", + "@react-types/menu": "3.9.14" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/dropdown/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/form": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/@heroui/form/-/form-2.1.14.tgz", + "integrity": "sha512-4OZ8dqekSkXca1hbhU1gUBEdrVm6QHafrzbyTpDHaohwE+ea+OuWKn5ttlS/S5wamcnxTErmG3uyvsZGsDspBw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/system": "2.4.11", + "@heroui/theme": "2.4.11", + "@react-aria/utils": "3.27.0", + "@react-stately/form": "3.1.1", + "@react-types/form": "3.7.9", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@heroui/form/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/framer-utils": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@heroui/framer-utils/-/framer-utils-2.1.11.tgz", + "integrity": "sha512-E8MLdLKvIVlAEVVeivORivt0HzN9y9LunmcOok3BhrBhSI0yBVBMrgd4XZJzFsNipgu2L687nxHbWeUs8jK/5g==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7", + "@heroui/system": "2.4.11", + "@heroui/use-measure": "2.1.6" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/image": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/image/-/image-2.2.10.tgz", + "integrity": "sha512-02v0bJShCwaoXAjfMLGV900HsJ4J5YtW3OHJD/TIGWQzHNYxv7Mls4u2PyfUpk6IDimlZ+fIiEjfV0zR/HY3MA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-image": "2.1.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/input": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@heroui/input/-/input-2.4.15.tgz", + "integrity": "sha512-ImnbIQ6h6gHgYTho1l+t+rzEehl86HJsshycoAfCi0Pn5U9KAe0Xv/ePZ6PeVhCC25QUcv1Ey5G2AkgobF49tg==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/textfield": "3.16.0", + "@react-aria/utils": "3.27.0", + "@react-stately/utils": "3.10.5", + "@react-types/shared": "3.27.0", + "@react-types/textfield": "3.11.0", + "react-textarea-autosize": "^8.5.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/input-otp": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/@heroui/input-otp/-/input-otp-2.1.14.tgz", + "integrity": "sha512-3Nd3+6zRxxIX6rSwdAoCm1eIqsyLqGjnFQhVG55FCyeWllfl3OQEwe3QZuQvpeBUVudeqBFCu7m2+Wbkv+r83g==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/form": "3.0.12", + "@react-aria/utils": "3.27.0", + "@react-stately/form": "3.1.1", + "@react-stately/utils": "3.10.5", + "@react-types/textfield": "3.11.0", + "input-otp": "1.4.1" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@heroui/input-otp/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/input/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/kbd": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/kbd/-/kbd-2.2.11.tgz", + "integrity": "sha512-yM1B4lxW2tgptKDt9rdw7T/HDEHGZLoSvB9V6X+0vJ/AjQMFZcqAIrYEUkM8BupX+Mokrur7dc036Mo3pYk5ww==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/system-rsc": "2.3.10", + "@react-aria/utils": "3.27.0" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/kbd/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/link": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/link/-/link-2.2.12.tgz", + "integrity": "sha512-LQTD9xSwEosgMHjZJfc0kN3AmBJfRv8W7LRuevHanSwusYvyreHGRoESIjPnTJ1+KNC01SuJlHt3K0y+JCkSfg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-link": "2.2.10", + "@react-aria/focus": "3.19.1", + "@react-aria/link": "3.7.8", + "@react-aria/utils": "3.27.0", + "@react-types/link": "3.5.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/link/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/listbox": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@heroui/listbox/-/listbox-2.3.14.tgz", + "integrity": "sha512-PANgexYzI7L9em6F/va0TzFrnPOLMgSWKgUTcmdAGrOefWnD7CwxyFeduiNNNDZ1m/WmLzWPwjPLDTxahHIfpw==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/divider": "2.2.10", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-is-mobile": "2.2.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/listbox": "3.14.0", + "@react-aria/utils": "3.27.0", + "@react-stately/list": "3.11.2", + "@react-types/menu": "3.9.14", + "@react-types/shared": "3.27.0", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/listbox/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/menu": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/menu/-/menu-2.2.14.tgz", + "integrity": "sha512-0CqyB2tsL/6ftxMShndyFHZySIUuqJHy4i3v7CP6NbnYv7oL6HVRB3ysg6qVaiXILF5S8HTTJNU1/kYvVgjfUQ==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/divider": "2.2.10", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-is-mobile": "2.2.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/menu": "3.17.0", + "@react-aria/utils": "3.27.0", + "@react-stately/menu": "3.9.1", + "@react-stately/tree": "3.8.7", + "@react-types/menu": "3.9.14", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/menu/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/modal": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/modal/-/modal-2.2.12.tgz", + "integrity": "sha512-kWoghgH/oihaBv0PzkfLbKDXWEZRVUHUEaxHCTI5Ut/5rAwxR/tYW9Wy+UW6m5nshefsq+RYh5cD9iPvDv4T1A==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-button": "2.2.9", + "@heroui/use-aria-modal-overlay": "2.2.8", + "@heroui/use-disclosure": "2.2.7", + "@heroui/use-draggable": "2.1.7", + "@react-aria/dialog": "3.5.21", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-stately/overlays": "3.6.13", + "@react-types/overlays": "3.8.12" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/modal/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/navbar": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@heroui/navbar/-/navbar-2.2.13.tgz", + "integrity": "sha512-gXg0j0rDxKG2SGZF/8x62O6K+yKcMXgVYx1Bs7yCvOO0Nc3PtOfkShxZqCMxY9d5qYHEI2hq3QvxjcDVXIoypg==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-scroll-position": "2.1.6", + "@react-aria/button": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-stately/toggle": "3.8.1", + "@react-stately/utils": "3.10.5" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/navbar/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/number-input": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@heroui/number-input/-/number-input-2.0.5.tgz", + "integrity": "sha512-GgvF3jPYzYG2fNpVYDC2wFjKRdJbM987enHzMheK2PaRo0Kf8nNaxVQZX/ZFJY1KVJeAHvQ/QqSDEHBmDp+5rQ==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.15", + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/numberfield": "3.11.10", + "@react-aria/utils": "3.27.0", + "@react-stately/numberfield": "3.9.9", + "@react-stately/utils": "3.10.5", + "@react-types/button": "3.10.2", + "@react-types/numberfield": "3.8.8", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/number-input/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/pagination": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@heroui/pagination/-/pagination-2.2.13.tgz", + "integrity": "sha512-TJankzcEb9unzvVKL6nWbvxD4U1RPhDZlkO26LylIRW7/bLHbeehJeY0a2yHxIIVjtiaNTyQK2MV9PUXdtKfrg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-intersection-observer": "2.2.7", + "@heroui/use-pagination": "2.2.8", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/pagination/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/popover": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@heroui/popover/-/popover-2.3.15.tgz", + "integrity": "sha512-KPLRB5L5a185pyCjyFDanGfVb8x/jni4wNpZB0OaPmOHj1/uIDACkmtavIqxIHu8IzrRGnAO1c0nt6iNg2qjfA==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/button": "2.2.15", + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-aria-button": "2.2.9", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/dialog": "3.5.21", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-stately/overlays": "3.6.13", + "@react-types/button": "3.10.2", + "@react-types/overlays": "3.8.12" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/popover/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/progress": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/progress/-/progress-2.2.11.tgz", + "integrity": "sha512-y7GtCxqkCyZr4tifDOS4/XAxgBH6+PqKOmuPC9pHCRL7b8VFeKM9KFkxjS6n6VM/zdKqnmlysdHDFtIi3ppVBw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-is-mounted": "2.1.6", + "@react-aria/i18n": "3.12.5", + "@react-aria/progress": "3.4.19", + "@react-aria/utils": "3.27.0", + "@react-types/progress": "3.5.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/progress/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/radio": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@heroui/radio/-/radio-2.3.14.tgz", + "integrity": "sha512-k0KSH8Q7D8pacpu9pZxobH3MJ/zg8MGvDWj0J0TY9ZpUnetSTG+YBuwTtlRGDKz8IBmu7kZ3rlym8NU6wbir2g==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/radio": "3.10.11", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/radio": "3.10.10", + "@react-types/radio": "3.8.6", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/radio/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/radio/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/react": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/@heroui/react/-/react-2.7.4.tgz", + "integrity": "sha512-w5RZxxx0DHxciLRsJ2v8oPeHIc/FH1sVuWUGM9Vjg5/0TZoZNYryDtsubMJLKAe3Yu010kxY9qOdR8GA3f25eg==", + "license": "MIT", + "dependencies": { + "@heroui/accordion": "2.2.12", + "@heroui/alert": "2.2.15", + "@heroui/autocomplete": "2.3.16", + "@heroui/avatar": "2.2.11", + "@heroui/badge": "2.2.10", + "@heroui/breadcrumbs": "2.2.11", + "@heroui/button": "2.2.15", + "@heroui/calendar": "2.2.15", + "@heroui/card": "2.2.14", + "@heroui/checkbox": "2.3.14", + "@heroui/chip": "2.2.11", + "@heroui/code": "2.2.11", + "@heroui/date-input": "2.3.14", + "@heroui/date-picker": "2.3.15", + "@heroui/divider": "2.2.10", + "@heroui/drawer": "2.2.12", + "@heroui/dropdown": "2.3.15", + "@heroui/form": "2.1.14", + "@heroui/framer-utils": "2.1.11", + "@heroui/image": "2.2.10", + "@heroui/input": "2.4.15", + "@heroui/input-otp": "2.1.14", + "@heroui/kbd": "2.2.11", + "@heroui/link": "2.2.12", + "@heroui/listbox": "2.3.14", + "@heroui/menu": "2.2.14", + "@heroui/modal": "2.2.12", + "@heroui/navbar": "2.2.13", + "@heroui/number-input": "2.0.5", + "@heroui/pagination": "2.2.13", + "@heroui/popover": "2.3.15", + "@heroui/progress": "2.2.11", + "@heroui/radio": "2.3.14", + "@heroui/ripple": "2.2.12", + "@heroui/scroll-shadow": "2.3.10", + "@heroui/select": "2.4.15", + "@heroui/skeleton": "2.2.10", + "@heroui/slider": "2.4.12", + "@heroui/snippet": "2.2.16", + "@heroui/spacer": "2.2.11", + "@heroui/spinner": "2.2.12", + "@heroui/switch": "2.2.13", + "@heroui/system": "2.4.11", + "@heroui/table": "2.2.14", + "@heroui/tabs": "2.2.12", + "@heroui/theme": "2.4.11", + "@heroui/toast": "2.0.5", + "@heroui/tooltip": "2.2.12", + "@heroui/user": "2.2.11", + "@react-aria/visually-hidden": "3.8.19" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/react-rsc-utils": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/react-rsc-utils/-/react-rsc-utils-2.1.6.tgz", + "integrity": "sha512-slBWi9g3HdnSNRhoedDhXFybaab5MveAeECzQoj4oJrIlmiezyeZWRKbWR8li2tiZtvBoEr0Xpu/A8hdni15dQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/react-utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@heroui/react-utils/-/react-utils-2.1.8.tgz", + "integrity": "sha512-ET8sQaqfAWEviuZfatSYXBzyD0PpzuIK2YQkijla0TmF0sHJ3Yl4YQ6DYleWAaIJEWW1u0HgUPrdIjVGjWyKVg==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.6", + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/react/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/ripple": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/ripple/-/ripple-2.2.12.tgz", + "integrity": "sha512-5hKlJfl05rtp/ABhmsJ/qqQjh9TgzyvBdeuvWf0K3PJVIMSp+LJly86mwlEzHEbbBwAJvdq9jxd3+R54ZMaQRw==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.6", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/scroll-shadow": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@heroui/scroll-shadow/-/scroll-shadow-2.3.10.tgz", + "integrity": "sha512-l10qKwQLWxW0l94SNxh+z8UnzgWlhTmvNRezrjXZZFhv4EKgv8u1f/E0HsLTy/g8KgPU0ebGWQmbhdqfMyiqOg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-data-scroll-overflow": "2.2.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/select": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@heroui/select/-/select-2.4.15.tgz", + "integrity": "sha512-FSbZd+M9RR/CY1hLJJ9bMQ6FbKQQS+f4jC6GGv2NAqx5kJ1wB1XjGpRRTPyZm1eKKLHc347DszOZRlYAMTxRxA==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/form": "2.1.14", + "@heroui/listbox": "2.3.14", + "@heroui/popover": "2.3.15", + "@heroui/react-utils": "2.1.8", + "@heroui/scroll-shadow": "2.3.10", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/spinner": "2.2.12", + "@heroui/use-aria-button": "2.2.9", + "@heroui/use-aria-multiselect": "2.4.8", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/focus": "3.19.1", + "@react-aria/form": "3.0.12", + "@react-aria/interactions": "3.23.0", + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-types/shared": "3.27.0", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/select/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/select/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/shared-icons": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/shared-icons/-/shared-icons-2.1.6.tgz", + "integrity": "sha512-4Gey+FJF4XBlMw5p9D2geOEAED8xCxuksurWKUz7eAoAivRRsZJf9wwUsKvNfrmboBUoytdxpUDbVgnckx/G8A==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/shared-utils": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/shared-utils/-/shared-utils-2.1.7.tgz", + "integrity": "sha512-1nx7y41P+Bsca7nDC+QFajAoFhSRGvjKhdFeopMQNTvU95L42PD7B0ThjcOretvQD0Ye2TsAEQInwsSgZ6kK/g==", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@heroui/skeleton": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/skeleton/-/skeleton-2.2.10.tgz", + "integrity": "sha512-6nv+Efzi3DBrVCVTY1CC8InaiYdmztPjmw/ytjGEm1rJNpJCK9HOgKSUVuz6dncLsIsB77toMfE+2s53Yrq9Yg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/slider": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@heroui/slider/-/slider-2.4.12.tgz", + "integrity": "sha512-ArIQ73SDKX3OL0Q8DX2aeGnPSnVaALtoYzlFthI3BzN1eQeWAPbf43Np29yMadBnuPhnBKBuSZkgGS0ziRtSsg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/tooltip": "2.2.12", + "@react-aria/focus": "3.19.1", + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/slider": "3.7.15", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/slider": "3.6.1" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/slider/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/slider/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/snippet": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/snippet/-/snippet-2.2.16.tgz", + "integrity": "sha512-QbWkGto4sP8L0LX3ZEqi5+vxWBFnRvjt1/zELY/NEFZLgMxC2BsM9uHc3ivxNNWyaV6S50Ddnl3RaL11+ZUd1g==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.15", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/tooltip": "2.2.12", + "@heroui/use-clipboard": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/utils": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/snippet/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/spacer": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/spacer/-/spacer-2.2.11.tgz", + "integrity": "sha512-NZp0pbZ8LJcR2XaslHPHFYJSKxStN+gO8eymNIQjumSRBU0cHpNcrxbNy/N3gBtCqOrrainbmSIXxpxifWeOyA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/system-rsc": "2.3.10" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/spinner": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/spinner/-/spinner-2.2.12.tgz", + "integrity": "sha512-P7tbxZg3E2vd2+5cpvNme1b6ytAy+BWEh7Q3JHjiIMjR70+140HulChp0nRkxIizoKF+vY5qGwEz+rAvteJ9dg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/system": "2.4.11", + "@heroui/system-rsc": "2.3.10" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/switch": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@heroui/switch/-/switch-2.2.13.tgz", + "integrity": "sha512-lPxGTZewHtSe2WfsMBoQajbr0NMfDSw8bHZWqpoe+ks+d8RCDNkULknkziFb5xzRQNfUoa6cnQ5P0vGqcG9YIw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/switch": "3.6.11", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/toggle": "3.8.1", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/switch/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/switch/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/system": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@heroui/system/-/system-2.4.11.tgz", + "integrity": "sha512-6OI7bgRLaH8i8kATeB6WOOLkHoxxSbfRaJr21bb2w+tnHkeqqHU73pURUR+t8jlheP9iS/j1IqGrcjVB9HR6sg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/system-rsc": "2.3.10", + "@internationalized/date": "3.7.0", + "@react-aria/i18n": "3.12.5", + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-stately/utils": "3.10.5", + "@react-types/datepicker": "3.10.0" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/system-rsc": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@heroui/system-rsc/-/system-rsc-2.3.10.tgz", + "integrity": "sha512-/feLXGslWl27fGfot3ePaku53+HMqMeKwuwvJ5z6xGl3W9Npxmy+rTIS31wd7lnVWfH1ARxyODJWcfEvp/5lgg==", + "license": "MIT", + "dependencies": { + "@react-types/shared": "3.27.0", + "clsx": "^1.2.1" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/system-rsc/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@heroui/system/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/table": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/table/-/table-2.2.14.tgz", + "integrity": "sha512-679UslGs8/UXp8V6ONnZIIr278jR6oKSv1XVM7+70MX2wpAieXiBcFdTvycXGRcEKQIsy+6AV4EqwTh5qizoMg==", + "license": "MIT", + "dependencies": { + "@heroui/checkbox": "2.3.14", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/spacer": "2.2.11", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/table": "3.16.1", + "@react-aria/utils": "3.27.0", + "@react-aria/visually-hidden": "3.8.19", + "@react-stately/table": "3.13.1", + "@react-stately/virtualizer": "4.2.1", + "@react-types/grid": "3.2.11", + "@react-types/table": "3.10.4", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/table/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/table/node_modules/@react-aria/visually-hidden": { + "version": "3.8.19", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.19.tgz", + "integrity": "sha512-MZgCCyQ3sdG94J5iJz7I7Ai3IxoN0U5d/+EaUnA1mfK7jf2fSYQBqi6Eyp8sWUYzBTLw4giXB5h0RGAnWzk9hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/tabs": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/tabs/-/tabs-2.2.12.tgz", + "integrity": "sha512-yMQCumzqbCS7YGfn/nmarBEWuNAqfGy6fz+bn3uUyHSkKc4CsiRUprzF5jJoHfOFGxQI+7XIBSOtQ262Pj5o1w==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-is-mounted": "2.1.6", + "@heroui/use-update-effect": "2.1.6", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/tabs": "3.9.9", + "@react-aria/utils": "3.27.0", + "@react-stately/tabs": "3.7.1", + "@react-types/shared": "3.27.0", + "@react-types/tabs": "3.3.12", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/tabs/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/theme": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.11.tgz", + "integrity": "sha512-sy17DlHCxi5qIILkVhzsez7ZsibWo5gBvZlBETp3yHtxrRwCEHqJ00x2frPAvDf2s/xiN79rtwMf5tlA4w3Ubw==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7", + "clsx": "^1.2.1", + "color": "^4.2.3", + "color2k": "^2.0.3", + "deepmerge": "4.3.1", + "flat": "^5.0.2", + "tailwind-merge": "2.5.4", + "tailwind-variants": "0.3.0" + }, + "peerDependencies": { + "tailwindcss": ">=3.4.0" + } + }, + "node_modules/@heroui/theme/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@heroui/theme/node_modules/tailwind-merge": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", + "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/@heroui/theme/node_modules/tailwind-variants": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", + "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/@heroui/toast": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@heroui/toast/-/toast-2.0.5.tgz", + "integrity": "sha512-EFSJ8F1K79q2Y9ssMQhuWWeNcDAT8HMdwsZxL3hV3MZl/ZsEFobmmCeH38gCTidm63CUiIL6zaT1/FI7F+QiKQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/shared-icons": "2.1.6", + "@heroui/shared-utils": "2.1.7", + "@heroui/spinner": "2.2.12", + "@heroui/use-is-mobile": "2.2.7", + "@react-aria/interactions": "3.23.0", + "@react-aria/toast": "3.0.0-beta.19", + "@react-aria/utils": "3.27.0", + "@react-stately/toast": "3.0.0-beta.7", + "@react-stately/utils": "3.10.5" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/toast/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/tooltip": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/tooltip/-/tooltip-2.2.12.tgz", + "integrity": "sha512-S9Hpc+DvK+E1lKEahBDGrIImzC99cVLjtB3bkv3u0zAhkEFfTs9JM5ti4wNWANG7i7G80MZemNRXHA0LQQmCtg==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.12", + "@heroui/dom-animation": "2.1.6", + "@heroui/framer-utils": "2.1.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@heroui/use-safe-layout-effect": "2.1.6", + "@react-aria/interactions": "3.23.0", + "@react-aria/overlays": "3.25.0", + "@react-aria/tooltip": "3.7.11", + "@react-aria/utils": "3.27.0", + "@react-stately/tooltip": "3.5.1", + "@react-types/overlays": "3.8.12", + "@react-types/tooltip": "3.4.14" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/tooltip/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-accordion": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-accordion/-/use-aria-accordion-2.2.7.tgz", + "integrity": "sha512-UPQoroLcvylGN3rSo3r350UPCl49Ava1HRjBsozqYHvTlG3PzW3ArnzDHwYjl3eh2dCtVyxDct+H4kokR2f0Ww==", + "license": "MIT", + "dependencies": { + "@react-aria/button": "3.11.1", + "@react-aria/focus": "3.19.1", + "@react-aria/selection": "3.22.0", + "@react-aria/utils": "3.27.0", + "@react-stately/tree": "3.8.7", + "@react-types/accordion": "3.0.0-alpha.26", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-accordion/node_modules/@react-aria/selection": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.22.0.tgz", + "integrity": "sha512-XFOrK525HX2eeWeLZcZscUAs5qsuC1ZxsInDXMjvLeAaUPtQNEhUKHj3psDAl6XDU4VV1IJo0qCmFTVqTTMZSg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/selection": "^3.19.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-accordion/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-button": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-button/-/use-aria-button-2.2.9.tgz", + "integrity": "sha512-hKYT8M98XDtCygGvMdCut4LpQgIRl8tKjTp7rZklWLPu3NgX2BBw0Lu2c4cjjf0XBWXMA/bTIIX9fJsd2FD6OA==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-types/button": "3.10.2", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-button/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-link": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-link/-/use-aria-link-2.2.10.tgz", + "integrity": "sha512-W8iNzUKMAWuZ7FrNxYS6HCGZLCZy91sgcMisE3GRrdXideqwb5F2OUfEY3FOYPoWDb2+AfhuKMsVfAdECS2nXQ==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/interactions": "3.23.0", + "@react-aria/utils": "3.27.0", + "@react-types/link": "3.5.10", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-link/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-modal-overlay": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-modal-overlay/-/use-aria-modal-overlay-2.2.8.tgz", + "integrity": "sha512-QYYF1zM+hZjUPnx40yDbhynH/d+2hEoYlduP6yhBXFiAk44mwm9hkJlLobQNZeiA+sobbN7EvguCYSoyYsOGYg==", + "license": "MIT", + "dependencies": { + "@react-aria/overlays": "3.25.0", + "@react-aria/utils": "3.27.0", + "@react-stately/overlays": "3.6.13", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-modal-overlay/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-multiselect": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-multiselect/-/use-aria-multiselect-2.4.8.tgz", + "integrity": "sha512-pvwCLpmP8SL/38EJme794TCP9wR5iQPeIctZbgncXZxT9qmDONn/bO27XFWTbWJlSf3hJ38Nm0HmRgUcat6sUw==", + "license": "MIT", + "dependencies": { + "@react-aria/i18n": "3.12.5", + "@react-aria/interactions": "3.23.0", + "@react-aria/label": "3.7.14", + "@react-aria/listbox": "3.14.0", + "@react-aria/menu": "3.17.0", + "@react-aria/selection": "3.22.0", + "@react-aria/utils": "3.27.0", + "@react-stately/form": "3.1.1", + "@react-stately/list": "3.11.2", + "@react-stately/menu": "3.9.1", + "@react-types/button": "3.10.2", + "@react-types/overlays": "3.8.12", + "@react-types/select": "3.9.9", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-multiselect/node_modules/@react-aria/selection": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.22.0.tgz", + "integrity": "sha512-XFOrK525HX2eeWeLZcZscUAs5qsuC1ZxsInDXMjvLeAaUPtQNEhUKHj3psDAl6XDU4VV1IJo0qCmFTVqTTMZSg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/selection": "^3.19.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-multiselect/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-callback-ref": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-callback-ref/-/use-callback-ref-2.1.6.tgz", + "integrity": "sha512-icFp4WBWTZhypBcyu+5kir7nZLtvtQq7DDvGwkTtxsGnFHgGDc6sXXcOU6AcCdoGefmsiVp5c3D3lZ2pMlGHmA==", + "license": "MIT", + "dependencies": { + "@heroui/use-safe-layout-effect": "2.1.6" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-clipboard": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-clipboard/-/use-clipboard-2.1.7.tgz", + "integrity": "sha512-Nt/ILhHovvYpoRjhqbbyz9sPI5xquvsSU/UuZ4qE8xFrsI8ukJo9znI1mW5eeNUlY9EOjz6HWdYU1B6QyLR3hg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-data-scroll-overflow": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@heroui/use-data-scroll-overflow/-/use-data-scroll-overflow-2.2.7.tgz", + "integrity": "sha512-+XPWShncxvPt+wSz5wXIP1GRws6mZs5QoHHG9n0agPL3eYiE0dHeEVYmfLQCopYhnnTA3HRcTkRKQ6pNR4oVQQ==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-disclosure": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@heroui/use-disclosure/-/use-disclosure-2.2.7.tgz", + "integrity": "sha512-nSv2MgoWS/7MKpZGikIT1CID0VgomSs2XTCz2M011VD89v0A9k27qW68Qmf03oWyrl6UghC1XdgJe6IIsfq72A==", + "license": "MIT", + "dependencies": { + "@heroui/use-callback-ref": "2.1.6", + "@react-aria/utils": "3.27.0", + "@react-stately/utils": "3.10.5" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-disclosure/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-draggable": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-draggable/-/use-draggable-2.1.7.tgz", + "integrity": "sha512-TMeUiDT1yw2sSIPardaB3/JIFD12VO1pV3N/Jf0WyiYQO82v2VoturQHbRTvjI+69SjqapucZSYM0X7henlOwg==", + "license": "MIT", + "dependencies": { + "@react-aria/interactions": "3.23.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-image": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-image/-/use-image-2.1.7.tgz", + "integrity": "sha512-Cno8oXNo/3YDCRnCwSuJYgdsZ7mujjVWSwlYaoYbi+rM5o9TjZYRPYHZacHMABlbY+Hew31ddYpOmyw4SrkIwA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.8", + "@heroui/use-safe-layout-effect": "2.1.6" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-intersection-observer": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@heroui/use-intersection-observer/-/use-intersection-observer-2.2.7.tgz", + "integrity": "sha512-efEAGWlfH9+DoShOSC/3dBmxtGDsVCwIQuV+ZBAROjbal/QOCOBKYOGpMcIBruOsAs8r4I9R432QhMxkDgOTWA==", + "license": "MIT", + "dependencies": { + "@react-aria/interactions": "3.23.0", + "@react-aria/ssr": "3.9.7", + "@react-aria/utils": "3.27.0", + "@react-types/shared": "3.27.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-intersection-observer/node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-intersection-observer/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-is-mobile": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@heroui/use-is-mobile/-/use-is-mobile-2.2.7.tgz", + "integrity": "sha512-aaQjvATBb09c4UzkcCaeZLqv5Sz0gtA1n07LxW+LJd2ENEYEuxNOWyO7dIAHaaYb3znX1ZxGC1h4cYLcN59nPA==", + "license": "MIT", + "dependencies": { + "@react-aria/ssr": "3.9.7" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-is-mobile/node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-is-mounted": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-is-mounted/-/use-is-mounted-2.1.6.tgz", + "integrity": "sha512-dnTX1PUWGhIQJxszTScHgM9XxvYIx9j8vnSJuVGaptJonZWlt50yI/WAi+oWXJ289rw7XBDJ8o38qmU5Pmq+WA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-measure": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-measure/-/use-measure-2.1.6.tgz", + "integrity": "sha512-FiN3Za6hExqU1B0d2drCm9JUFneQ1W5gyNoX0owf3aIWG98QR+LR1MOL3WBAGWtDsp4K6q8rqUKXatNxGJd/sA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-pagination": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@heroui/use-pagination/-/use-pagination-2.2.8.tgz", + "integrity": "sha512-SQNJli3A5uoTl9f5xKsgkXGG8YuNUtCaAfpb+f1xQ5/v0QEBuhttRH6njT76+wXIlijSK42b2bSzU3EKY2uGjg==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.7", + "@react-aria/i18n": "3.12.5" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-safe-layout-effect": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-safe-layout-effect/-/use-safe-layout-effect-2.1.6.tgz", + "integrity": "sha512-yLT6zrlcZGJX4KKenzvR6lPS42Lf/Q0Q8ErpufLSkTdX4uk/ThGB/CRwdXfP+TPFLIfjXdsgCHgZr2ZAQJaG5Q==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-scroll-position": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-scroll-position/-/use-scroll-position-2.1.6.tgz", + "integrity": "sha512-9ap2AIuPjJCGLt7ZZAQqSE7s9Md1lUqnmxXf6UhKH0CJowhVHIl76gtV2rMeQZ+vsjbG3d4tsX2Vw13h+HLpuA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-update-effect": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@heroui/use-update-effect/-/use-update-effect-2.1.6.tgz", + "integrity": "sha512-nGSaIngKPuutmQcfZgnMHGYXJDqo6sPjdIIFjb5vutEnc827Xyh5f4q8hXfo7huYYYzA1CqLaThNVFCf3qIwHg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/user": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/user/-/user-2.2.11.tgz", + "integrity": "sha512-1ZpVIr7yjdJEeFPWtvHmx0Q8WJSkKYnUd5iz0m9z8pTrrQtnfWavdwsGJ5Xw68Sjsr/ig5SEjro5Yn6l4COn8A==", + "license": "MIT", + "dependencies": { + "@heroui/avatar": "2.2.11", + "@heroui/react-utils": "2.1.8", + "@heroui/shared-utils": "2.1.7", + "@react-aria/focus": "3.19.1", + "@react-aria/utils": "3.27.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/user/node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@internationalized/date": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz", + "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/message": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.8.tgz", + "integrity": "sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "intl-messageformat": "^10.1.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.7.tgz", + "integrity": "sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@react-aria/breadcrumbs": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.20.tgz", + "integrity": "sha512-xqVSSDPpQuUFpJyIXMQv8L7zumk5CeGX7qTzo4XRvqm5T9qnNAX4XpYEMdktnLrQRY/OemCBScbx7SEwr0B3Kg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.5", + "@react-aria/link": "^3.7.8", + "@react-aria/utils": "^3.27.0", + "@react-types/breadcrumbs": "^3.7.10", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/button": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.11.1.tgz", + "integrity": "sha512-NSs2HxHSSPSuYy5bN+PMJzsCNDVsbm1fZ/nrWM2WWWHTBrx9OqyrEXZVV9ebzQCN9q0nzhwpf6D42zHIivWtJA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/interactions": "^3.23.0", + "@react-aria/toolbar": "3.0.0-beta.12", + "@react-aria/utils": "^3.27.0", + "@react-stately/toggle": "^3.8.1", + "@react-types/button": "^3.10.2", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/calendar": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.7.0.tgz", + "integrity": "sha512-9YUbgcox7cQgvZfQtL2BLLRsIuX4mJeclk9HkFoOsAu3RGO5HNsteah8FV54W8BMjm/bNRXIPUxtjTTP+1L6jg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/live-announcer": "^3.4.1", + "@react-aria/utils": "^3.27.0", + "@react-stately/calendar": "^3.7.0", + "@react-types/button": "^3.10.2", + "@react-types/calendar": "^3.6.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/checkbox": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.15.1.tgz", + "integrity": "sha512-ETgsMDZ0IZzRXy/OVlGkazm8T+PcMHoTvsxp0c+U82c8iqdITA+VJ615eBPOQh6OkkYIIn4cRn/e+69RmGzXng==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.0.12", + "@react-aria/interactions": "^3.23.0", + "@react-aria/label": "^3.7.14", + "@react-aria/toggle": "^3.10.11", + "@react-aria/utils": "^3.27.0", + "@react-stately/checkbox": "^3.6.11", + "@react-stately/form": "^3.1.1", + "@react-stately/toggle": "^3.8.1", + "@react-types/checkbox": "^3.9.1", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/combobox": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.11.1.tgz", + "integrity": "sha512-TTNbGhUuqxzPcJzd6hufOxuHzX0UARkw+0bl+TuCwNPQnqrcPf20EoOZvd3MHZwGq6GCP4QV+qo0uGx83RpUvA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.5", + "@react-aria/listbox": "^3.14.0", + "@react-aria/live-announcer": "^3.4.1", + "@react-aria/menu": "^3.17.0", + "@react-aria/overlays": "^3.25.0", + "@react-aria/selection": "^3.22.0", + "@react-aria/textfield": "^3.16.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/collections": "^3.12.1", + "@react-stately/combobox": "^3.10.2", + "@react-stately/form": "^3.1.1", + "@react-types/button": "^3.10.2", + "@react-types/combobox": "^3.13.2", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/datepicker": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.13.0.tgz", + "integrity": "sha512-TmJan65P3Vk7VDBNW5rH9Z25cAn0vk8TEtaP3boCs8wJFE+HbEuB8EqLxBFu47khtuKTEqDP3dTlUh2Vt/f7Xw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@internationalized/number": "^3.6.0", + "@internationalized/string": "^3.2.5", + "@react-aria/focus": "^3.19.1", + "@react-aria/form": "^3.0.12", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/label": "^3.7.14", + "@react-aria/spinbutton": "^3.6.11", + "@react-aria/utils": "^3.27.0", + "@react-stately/datepicker": "^3.12.0", + "@react-stately/form": "^3.1.1", + "@react-types/button": "^3.10.2", + "@react-types/calendar": "^3.6.0", + "@react-types/datepicker": "^3.10.0", + "@react-types/dialog": "^3.5.15", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.21.tgz", + "integrity": "sha512-tBsn9swBhcptJ9QIm0+ur0PVR799N6qmGguva3rUdd+gfitknFScyT08d7AoMr9AbXYdJ+2R9XNSZ3H3uIWQMw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/overlays": "^3.25.0", + "@react-aria/utils": "^3.27.0", + "@react-types/dialog": "^3.5.15", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz", + "integrity": "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/form": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.12.tgz", + "integrity": "sha512-8uvPYEd3GDyGt5NRJIzdWW1Ry5HLZq37vzRZKUW8alZ2upFMH3KJJG55L9GP59KiF6zBrYBebvI/YK1Ye1PE1g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/form": "^3.1.1", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.5.tgz", + "integrity": "sha512-XHw6rgjlTqc85e3zjsWo3U0EVwjN5MOYtrolCKc/lc2ItNdcY3OlMhpsU9+6jHwg/U3VCSWkGvwAz9hg7krd8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.2", + "@react-aria/i18n": "^3.12.13", + "@react-aria/interactions": "^3.25.6", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/selection": "^3.26.0", + "@react-aria/utils": "^3.31.0", + "@react-stately/collections": "^3.12.8", + "@react-stately/grid": "^3.11.6", + "@react-stately/selection": "^3.20.6", + "@react-types/checkbox": "^3.10.2", + "@react-types/grid": "^3.3.6", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/focus": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz", + "integrity": "sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/i18n": { + "version": "3.12.13", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.13.tgz", + "integrity": "sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.10.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/checkbox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.10.2.tgz", + "integrity": "sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/grid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.6.tgz", + "integrity": "sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/i18n": { + "version": "3.12.5", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.5.tgz", + "integrity": "sha512-ooeop2pTG94PuaHoN2OTk2hpkqVuoqgEYxRvnc1t7DVAtsskfhS/gVOTqyWGsxvwAvRi7m/CnDu6FYdeQ/bK5w==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@internationalized/message": "^3.1.6", + "@internationalized/number": "^3.6.0", + "@internationalized/string": "^3.2.5", + "@react-aria/ssr": "^3.9.7", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.23.0.tgz", + "integrity": "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/label": { + "version": "3.7.14", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.14.tgz", + "integrity": "sha512-EN1Md2YvcC4sMqBoggsGYUEGlTNqUfJZWzduSt29fbQp1rKU2KlybTe+TWxKq/r2fFd+4JsRXxMeJiwB3w2AQA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/landmark": { + "version": "3.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.0-beta.18.tgz", + "integrity": "sha512-jFtWL7TYZrKucWNDx6ppUkGSqS2itkjhyLo9MIFqEg2mi4Lc2EoUjI/Gw9xMT+IJgebTcdQeXJpPskspl3Pojg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.8.tgz", + "integrity": "sha512-oiXUPQLZmf9Q9Xehb/sG1QRxfo28NFKdh9w+unD12sHI6NdLMETl5MA4CYyTgI0dfMtTjtfrF68GCnWfc7JvXQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/link": "^3.5.10", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/listbox": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.0.tgz", + "integrity": "sha512-pyVbKavh8N8iyiwOx6I3JIcICvAzFXkKSFni1yarfgngJsJV3KSyOkzLomOfN9UhbjcV4sX61/fccwJuvlurlA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/label": "^3.7.14", + "@react-aria/selection": "^3.22.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/collections": "^3.12.1", + "@react-stately/list": "^3.11.2", + "@react-types/listbox": "^3.5.4", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/live-announcer": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.4.4.tgz", + "integrity": "sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.17.0.tgz", + "integrity": "sha512-aiFvSv3G1YvPC0klJQ/9quB05xIDZzJ5Lt6/CykP0UwGK5i8GCqm6/cyFLwEXsS5ooUPxS3bqmdOsgdADSSgqg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/overlays": "^3.25.0", + "@react-aria/selection": "^3.22.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/collections": "^3.12.1", + "@react-stately/menu": "^3.9.1", + "@react-stately/selection": "^3.19.0", + "@react-stately/tree": "^3.8.7", + "@react-types/button": "^3.10.2", + "@react-types/menu": "^3.9.14", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/numberfield": { + "version": "3.11.10", + "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.10.tgz", + "integrity": "sha512-bYbTfO9NbAKMFOfEGGs+lvlxk0I9L0lU3WD2PFQZWdaoBz9TCkL+vK0fJk1zsuKaVjeGsmHP9VesBPRmaP0MiA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/spinbutton": "^3.6.11", + "@react-aria/textfield": "^3.16.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/form": "^3.1.1", + "@react-stately/numberfield": "^3.9.9", + "@react-types/button": "^3.10.2", + "@react-types/numberfield": "^3.8.8", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.25.0.tgz", + "integrity": "sha512-UEqJJ4duowrD1JvwXpPZreBuK79pbyNjNxFUVpFSskpGEJe3oCWwsSDKz7P1O7xbx5OYp+rDiY8fk/sE5rkaKw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/ssr": "^3.9.7", + "@react-aria/utils": "^3.27.0", + "@react-aria/visually-hidden": "^3.8.19", + "@react-stately/overlays": "^3.6.13", + "@react-types/button": "^3.10.2", + "@react-types/overlays": "^3.8.12", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/progress": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.19.tgz", + "integrity": "sha512-5HHnBJHqEUuY+dYsjIZDYsENeKr49VCuxeaDZ0OSahbOlloIOB1baCo/6jLBv1O1rwrAzZ2gCCPcVGed/cjrcw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.5", + "@react-aria/label": "^3.7.14", + "@react-aria/utils": "^3.27.0", + "@react-types/progress": "^3.5.9", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.10.11", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.11.tgz", + "integrity": "sha512-R150HsBFPr1jLMShI4aBM8heCa1k6h0KEvnFRfTAOBu+B9hMSZOPB+d6GQOwGPysNlbset90Kej8G15FGHjqiA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/form": "^3.0.12", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/label": "^3.7.14", + "@react-aria/utils": "^3.27.0", + "@react-stately/radio": "^3.10.10", + "@react-types/radio": "^3.8.6", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.26.0.tgz", + "integrity": "sha512-ZBH3EfWZ+RfhTj01dH8L17uT7iNbXWS8u77/fUpHgtrm0pwNVhx0TYVnLU1YpazQ/3WVpvWhmBB8sWwD1FlD/g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.2", + "@react-aria/i18n": "^3.12.13", + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-stately/selection": "^3.20.6", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection/node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/selection/node_modules/@react-aria/focus": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz", + "integrity": "sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection/node_modules/@react-aria/i18n": { + "version": "3.12.13", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.13.tgz", + "integrity": "sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.10.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.7.15", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.15.tgz", + "integrity": "sha512-v9tujsuvJYRX0vE/vMYBzTT9FXbzrLsjkOrouNq+UdBIr7wRjIWTHHM0j+khb2swyCWNTbdv6Ce316Zqx2qWFg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/label": "^3.7.14", + "@react-aria/utils": "^3.27.0", + "@react-stately/slider": "^3.6.1", + "@react-types/shared": "^3.27.0", + "@react-types/slider": "^3.7.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.19.tgz", + "integrity": "sha512-xOIXegDpts9t3RSHdIN0iYQpdts0FZ3LbpYJIYVvdEHo9OpDS+ElnDzCGtwZLguvZlwc5s1LAKuKopDUsAEMkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.13", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/utils": "^3.31.0", + "@react-types/button": "^3.14.1", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/i18n": { + "version": "3.12.13", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.13.tgz", + "integrity": "sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.10.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-types/button": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.14.1.tgz", + "integrity": "sha512-D8C4IEwKB7zEtiWYVJ3WE/5HDcWlze9mLWQ5hfsBfpePyWCgO3bT/+wjb/7pJvcAocrkXo90QrMm85LcpBtrpg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.11.tgz", + "integrity": "sha512-paYCpH+oeL+8rgQK+cBJ+IaZ1sXSh3+50WPlg2LvLBta0QVfQhPR4juPvfXRpfHHhCjFBgF4/RGbV8q5zpl3vA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/toggle": "^3.10.11", + "@react-stately/toggle": "^3.8.1", + "@react-types/shared": "^3.27.0", + "@react-types/switch": "^3.5.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.16.1.tgz", + "integrity": "sha512-T28TIGnKnPBunyErDBmm5jUX7AyzT7NVWBo9pDSt9wUuEnz0rVNd7p9sjmP2+u7I645feGG9klcdpCvFeqrk8A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/grid": "^3.11.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/live-announcer": "^3.4.1", + "@react-aria/utils": "^3.27.0", + "@react-aria/visually-hidden": "^3.8.19", + "@react-stately/collections": "^3.12.1", + "@react-stately/flags": "^3.0.5", + "@react-stately/table": "^3.13.1", + "@react-types/checkbox": "^3.9.1", + "@react-types/grid": "^3.2.11", + "@react-types/shared": "^3.27.0", + "@react-types/table": "^3.10.4", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tabs": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.9.9.tgz", + "integrity": "sha512-oXPtANs16xu6MdMGLHjGV/2Zupvyp9CJEt7ORPLv5xAzSY5hSjuQHJLZ0te3Lh/KSG5/0o3RW/W5yEqo7pBQQQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/selection": "^3.22.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/tabs": "^3.7.1", + "@react-types/shared": "^3.27.0", + "@react-types/tabs": "^3.3.12", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/textfield": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.16.0.tgz", + "integrity": "sha512-53RVpMeMDN/QoabqnYZ1lxTh1xTQ3IBYQARuayq5EGGMafyxoFHzttxUdSqkZGK/+zdSF2GfmjOYJVm2nDKuDQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/form": "^3.0.12", + "@react-aria/label": "^3.7.14", + "@react-aria/utils": "^3.27.0", + "@react-stately/form": "^3.1.1", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@react-types/textfield": "^3.11.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toast": { + "version": "3.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.0-beta.19.tgz", + "integrity": "sha512-LCMTcmSmum5CzBk+DIec66q6pJGEl+InQPJdsby7QG/row0ka6wHPvul78HVseN7dzg6G3xVjvHtVPOixkuegA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.5", + "@react-aria/interactions": "^3.23.0", + "@react-aria/landmark": "3.0.0-beta.18", + "@react-aria/utils": "^3.27.0", + "@react-stately/toast": "3.0.0-beta.7", + "@react-types/button": "^3.10.2", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.12.2.tgz", + "integrity": "sha512-g25XLYqJuJpt0/YoYz2Rab8ax+hBfbssllcEFh0v0jiwfk2gwTWfRU9KAZUvxIqbV8Nm8EBmrYychDpDcvW1kw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-stately/toggle": "^3.9.2", + "@react-types/checkbox": "^3.10.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-stately/toggle": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.9.2.tgz", + "integrity": "sha512-dOxs9wrVXHUmA7lc8l+N9NbTJMAaXcYsnNGsMwfXIXQ3rdq+IjWGNYJ52UmNQyRYFcg0jrzRrU16TyGbNjOdNQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/checkbox": "^3.10.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-types/checkbox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.10.2.tgz", + "integrity": "sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toolbar": { + "version": "3.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.12.tgz", + "integrity": "sha512-a+Be27BtM2lzEdTzm19FikPbitfW65g/JZln3kyAvgpswhU6Ljl8lztaVw4ixjG4H0nqnKvVggMy4AlWwDUaVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/i18n": "^3.12.5", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tooltip": { + "version": "3.7.11", + "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.11.tgz", + "integrity": "sha512-mhZgAWUj7bUWipDeJXaVPZdqnzoBCd/uaEbdafnvgETmov1udVqPTh9w4ZKX2Oh1wa2+OdLFrBOk+8vC6QbWag==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.19.1", + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-stately/tooltip": "^3.5.1", + "@react-types/shared": "^3.27.0", + "@react-types/tooltip": "^3.4.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", + "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils/node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.8.28", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.28.tgz", + "integrity": "sha512-KRRjbVVob2CeBidF24dzufMxBveEUtUu7IM+hpdZKB+gxVROoh4XRLPv9SFmaH89Z7D9To3QoykVZoWD0lan6Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.6", + "@react-aria/utils": "^3.31.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden/node_modules/@react-aria/interactions": { + "version": "3.25.6", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", + "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.31.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/calendar": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.7.0.tgz", + "integrity": "sha512-N15zKubP2S7eWfPSJjKVlmJA7YpWzrIGx52BFhwLSQAZcV+OPcMgvOs71WtB7PLwl6DUYQGsgc0B3tcHzzvdvQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@react-stately/utils": "^3.10.5", + "@react-types/calendar": "^3.6.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.11.tgz", + "integrity": "sha512-jApdBis+Q1sXLivg+f7krcVaP/AMMMiQcVqcz5gwxlweQN+dRZ/NpL0BYaDOuGc26Mp0lcuVaET3jIZeHwtyxA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.1.1", + "@react-stately/utils": "^3.10.5", + "@react-types/checkbox": "^3.9.1", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.1.tgz", + "integrity": "sha512-8QmFBL7f+P64dEP4o35pYH61/lP0T/ziSdZAvNMrCqaM+fXcMfUp2yu1E63kADVX7WRDsFJWE3CVMeqirPH6Xg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/combobox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.10.2.tgz", + "integrity": "sha512-uT642Dool4tQBh+8UQjlJnTisrJVtg3LqmiP/HqLQ4O3pW0O+ImbG+2r6c9dUzlAnH4kEfmEwCp9dxkBkmFWsg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.1", + "@react-stately/form": "^3.1.1", + "@react-stately/list": "^3.11.2", + "@react-stately/overlays": "^3.6.13", + "@react-stately/select": "^3.6.10", + "@react-stately/utils": "^3.10.5", + "@react-types/combobox": "^3.13.2", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/datepicker": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.12.0.tgz", + "integrity": "sha512-AfJEP36d+QgQ30GfacXtYdGsJvqY2yuCJ+JrjHct+m1nYuTkMvMMnhwNBFasgDJPLCDyHzyANlWkl2kQGfsBFw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@internationalized/string": "^3.2.5", + "@react-stately/form": "^3.1.1", + "@react-stately/overlays": "^3.6.13", + "@react-stately/utils": "^3.10.5", + "@react-types/datepicker": "^3.10.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/form": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.1.1.tgz", + "integrity": "sha512-qavrz5X5Mdf/Q1v/QJRxc0F8UTNEyRCNSM1we/nnF7GV64+aYSDLOtaRGmzq+09RSwo1c8ZYnIkK5CnwsPhTsQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.6.tgz", + "integrity": "sha512-vWPAkzpeTIsrurHfMubzMuqEw7vKzFhIJeEK5sEcLunyr1rlADwTzeWrHNbPMl66NAIAi70Dr1yNq+kahQyvMA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.8", + "@react-stately/selection": "^3.20.6", + "@react-types/grid": "^3.3.6", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-types/grid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.6.tgz", + "integrity": "sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/list": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.11.2.tgz", + "integrity": "sha512-eU2tY3aWj0SEeC7lH9AQoeAB4LL9mwS54FvTgHHoOgc1ZIwRJUaZoiuETyWQe98AL8KMgR1nrnDJ1I+CcT1Y7g==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.1", + "@react-stately/selection": "^3.19.0", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.9.1.tgz", + "integrity": "sha512-WRjGGImhQlQaer/hhahGytwd1BDq3fjpTkY/04wv3cQJPJR6lkVI5nSvGFMHfCaErsA1bNyB8/T9Y5F5u4u9ng==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.13", + "@react-types/menu": "^3.9.14", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/numberfield": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.9.tgz", + "integrity": "sha512-hZsLiGGHTHmffjFymbH1qVmA633rU2GNjMFQTuSsN4lqqaP8fgxngd5pPCoTCUFEkUgWjdHenw+ZFByw8lIE+g==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/number": "^3.6.0", + "@react-stately/form": "^3.1.1", + "@react-stately/utils": "^3.10.5", + "@react-types/numberfield": "^3.8.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.6.13", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.13.tgz", + "integrity": "sha512-WsU85Gf/b+HbWsnnYw7P/Ila3wD+C37Uk/WbU4/fHgJ26IEOWsPE6wlul8j54NZ1PnLNhV9Fn+Kffi+PaJMQXQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.5", + "@react-types/overlays": "^3.8.12", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.10.10", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.10.tgz", + "integrity": "sha512-9x3bpq87uV8iYA4NaioTTWjriQSlSdp+Huqlxll0T3W3okpyraTTejE91PbIoRTUmL5qByIh2WzxYmr4QdBgAA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.1.1", + "@react-stately/utils": "^3.10.5", + "@react-types/radio": "^3.8.6", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.8.0.tgz", + "integrity": "sha512-A721nlt0DSCDit0wKvhcrXFTG5Vv1qkEVkeKvobmETZy6piKvwh0aaN8iQno5AFuZaj1iOZeNjZ/20TsDJR/4A==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.2.2", + "@react-stately/list": "^3.13.1", + "@react-stately/overlays": "^3.6.20", + "@react-stately/utils": "^3.10.8", + "@react-types/select": "^3.11.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/form": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.2.2.tgz", + "integrity": "sha512-soAheOd7oaTO6eNs6LXnfn0tTqvOoe3zN9FvtIhhrErKz9XPc5sUmh3QWwR45+zKbitOi1HOjfA/gifKhZcfWw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/list": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.13.1.tgz", + "integrity": "sha512-eHaoauh21twbcl0kkwULhVJ+CzYcy1jUjMikNVMHOQdhr4WIBdExf7PmSgKHKqsSPhpGg6IpTCY2dUX3RycjDg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.8", + "@react-stately/selection": "^3.20.6", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/overlays": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.20.tgz", + "integrity": "sha512-YAIe+uI8GUXX8F/0Pzr53YeC5c/bjqbzDFlV8NKfdlCPa6+Jp4B/IlYVjIooBj9+94QvbQdjylegvYWK/iPwlg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/overlays": "^3.9.2", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/overlays": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.2.tgz", + "integrity": "sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/select": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.11.0.tgz", + "integrity": "sha512-SzIsMFVPCbXE1Z1TLfpdfiwJ1xnIkcL1/CjGilmUKkNk5uT7rYX1xCJqWCjXI0vAU1xM4Qn+T3n8de4fw6HRBg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.20.6", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.6.tgz", + "integrity": "sha512-a0bjuP2pJYPKEiedz2Us1W1aSz0iHRuyeQEdBOyL6Z6VUa6hIMq9H60kvseir2T85cOa4QggizuRV7mcO6bU5w==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.8", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection/node_modules/@react-stately/collections": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.8.tgz", + "integrity": "sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection/node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.6.1.tgz", + "integrity": "sha512-8kij5O82Xe233vZZ6qNGqPXidnlNQiSnyF1q613c7ktFmzAyGjkIWVUapHi23T1fqm7H2Rs3RWlmwE9bo2KecA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@react-types/slider": "^3.7.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/table": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.13.1.tgz", + "integrity": "sha512-Im8W+F8o9EhglY5kqRa3xcMGXl8zBi6W5phGpAjXb+UGDL1tBIlAcYj733bw8g/ITCnaSz9ubsmON0HekPd6Jg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.1", + "@react-stately/flags": "^3.0.5", + "@react-stately/grid": "^3.10.1", + "@react-stately/selection": "^3.19.0", + "@react-stately/utils": "^3.10.5", + "@react-types/grid": "^3.2.11", + "@react-types/shared": "^3.27.0", + "@react-types/table": "^3.10.4", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tabs": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.7.1.tgz", + "integrity": "sha512-gr9ACyuWrYuc727h7WaHdmNw8yxVlUyQlguziR94MdeRtFGQnf3V6fNQG3kxyB77Ljko69tgDF7Nf6kfPUPAQQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/list": "^3.11.2", + "@react-types/shared": "^3.27.0", + "@react-types/tabs": "^3.3.12", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toast": { + "version": "3.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@react-stately/toast/-/toast-3.0.0-beta.7.tgz", + "integrity": "sha512-+KDkaOS5Y4ApOfiP0HHij4ySwAd1VM9/pI4rVTyHrzkp0R2Q0eBxZ74MpWMpVfJa2lSjb/qEm60tqJ3A+4R/cQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.1.tgz", + "integrity": "sha512-MVpe79ghVQiwLmVzIPhF/O/UJAUc9B+ZSylVTyJiEPi0cwhbkKGQv9thOF0ebkkRkace5lojASqUAYtSTZHQJA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.5", + "@react-types/checkbox": "^3.9.1", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tooltip": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.5.1.tgz", + "integrity": "sha512-0aI3U5kB7Cop9OCW9/Bag04zkivFSdUcQgy/TWL4JtpXidVWmOha8txI1WySawFSjZhH83KIyPc+wKm1msfLMQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.13", + "@react-types/tooltip": "^3.4.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.7.tgz", + "integrity": "sha512-hpc3pyuXWeQV5ufQ02AeNQg/MYhnzZ4NOznlY5OOUoPzpLYiI3ZJubiY3Dot4jw5N/LR7CqvDLHmrHaJPmZlHg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.1", + "@react-stately/selection": "^3.19.0", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", + "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/virtualizer": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-4.2.1.tgz", + "integrity": "sha512-GHGEXV0ZRhq34U/P3LzkByCBfy2IDynYlV1SE4njkUWWGE/0AH56UegM6w2l3GeiNpXsXCgXl7jpAKeIGMEnrQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/accordion": { + "version": "3.0.0-alpha.26", + "resolved": "https://registry.npmjs.org/@react-types/accordion/-/accordion-3.0.0-alpha.26.tgz", + "integrity": "sha512-OXf/kXcD2vFlEnkcZy/GG+a/1xO9BN7Uh3/5/Ceuj9z2E/WwD55YwU3GFM5zzkZ4+DMkdowHnZX37XnmbyD3Mg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/breadcrumbs": { + "version": "3.7.10", + "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.10.tgz", + "integrity": "sha512-5HhRxkKHfAQBoyOYzyf4HT+24HgPE/C/QerxJLNNId303LXO03yeYrbvRqhYZSlD1ACLJW9OmpPpREcw5iSqgw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/link": "^3.5.10", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/button": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.10.2.tgz", + "integrity": "sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/calendar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.6.0.tgz", + "integrity": "sha512-BtFh4BFwvsYlsaSqUOVxlqXZSlJ6u4aozgO3PwHykhpemwidlzNwm9qDZhcMWPioNF/w2cU/6EqhvEKUHDnFZg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.1.tgz", + "integrity": "sha512-0x/KQcipfNM9Nvy6UMwYG25roRLvsiqf0J3woTYylNNWzF+72XT0iI5FdJkE3w2wfa0obmSoeq4WcbFREQrH/A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/combobox": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.13.2.tgz", + "integrity": "sha512-yl2yMcM5/v3lJiNZWjpAhQ9vRW6dD55CD4rYmO2K7XvzYJaFVT4WYI/AymPYD8RqomMp7coBmBHfHW0oupk8gg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/datepicker": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.10.0.tgz", + "integrity": "sha512-Att7y4NedNH1CogMDIX9URXgMLxGbZgnFCZ8oxgFAVndWzbh3TBcc4s7uoJDPvgRMAalq+z+SrlFFeoBeJmvvg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.7.0", + "@react-types/calendar": "^3.6.0", + "@react-types/overlays": "^3.8.12", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.22.tgz", + "integrity": "sha512-smSvzOcqKE196rWk0oqJDnz+ox5JM5+OT0PmmJXiUD4q7P5g32O6W5Bg7hMIFUI9clBtngo8kLaX2iMg+GqAzg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.9.2", + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog/node_modules/@react-types/overlays": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.2.tgz", + "integrity": "sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/form": { + "version": "3.7.9", + "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.9.tgz", + "integrity": "sha512-+qGDrQFdIh8umU82zmnYJ0V2rLoGSQ3yApFT02URz//NWeTA7qo0Oab2veKvXUkcBb47oSvytZYmkExPikxIEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/grid": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.11.tgz", + "integrity": "sha512-Mww9nrasppvPbsBi+uUqFnf7ya8fXN0cTVzDNG+SveD8mhW+sbtuy+gPtEpnFD2Oyi8qLuObefzt4gdekJX2Yw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/link": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.10.tgz", + "integrity": "sha512-IM2mbSpB0qP44Jh1Iqpevo7bQdZAr0iDyDi13OhsiUYJeWgPMHzGEnQqdBMkrfQeOTXLtZtUyOYLXE2v39bhzQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/listbox": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.4.tgz", + "integrity": "sha512-p4YEpTl/VQGrqVE8GIfqTS5LkT5jtjDTbVeZgrkPnX/fiPhsfbTPiZ6g0FNap4+aOGJFGEEZUv2q4vx+rCORww==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/listbox/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/menu": { + "version": "3.9.14", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.14.tgz", + "integrity": "sha512-RJW/S8IPwbRuohJ/A9HJ7W8QaAY816tm7Nv6+H/TLXG76zu2AS5vEgq+0TcCAWvJJwUdLDpJWJMlo0iIoIBtcg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.8.12", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/numberfield": { + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.8.tgz", + "integrity": "sha512-825JPppxDaWh0Zxb0Q+wSslgRQYOtQPCAuhszPuWEy6d2F/M+hLR+qQqvQm9+LfMbdwiTg6QK5wxdWFCp2t7jw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.8.12", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.12.tgz", + "integrity": "sha512-ZvR1t0YV7/6j+6OD8VozKYjvsXT92+C/2LOIKozy7YUNS5KI4MkXbRZzJvkuRECVZOmx8JXKTUzhghWJM/3QuQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/progress": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.9.tgz", + "integrity": "sha512-zFxOzx3G8XUmHgpm037Hcayls5bqzXVa182E3iM7YWTmrjxJPKZ58XL0WWBgpTd+mJD7fTpnFdAZqSmFbtDOdA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/radio": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.6.tgz", + "integrity": "sha512-woTQYdRFjPzuml4qcIf+2zmycRuM5w3fDS5vk6CQmComVUjOFPtD28zX3Z9kc9lSNzaBQz9ONZfFqkZ1gqfICA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/select": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.9.tgz", + "integrity": "sha512-/hCd0o+ztn29FKCmVec+v7t4JpOzz56o+KrG7NDq2pcRWqUR9kNwCjrPhSbJIIEDm4ubtrfPu41ysIuDvRd2Bg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz", + "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/slider": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.8.2.tgz", + "integrity": "sha512-MQYZP76OEOYe7/yA2To+Dl0LNb0cKKnvh5JtvNvDnAvEprn1RuLiay8Oi/rTtXmc2KmBa4VdTcsXsmkbbkeN2Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/slider/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/switch": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.15.tgz", + "integrity": "sha512-r/ouGWQmIeHyYSP1e5luET+oiR7N7cLrAlWsrAfYRWHxqXOSNQloQnZJ3PLHrKFT02fsrQhx2rHaK2LfKeyN3A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/switch/node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/table": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.10.4.tgz", + "integrity": "sha512-d0tLz/whxVteqr1rophtuuxqyknHHfTKeXrCgDjt8pAyd9U8GPDbfcFSfYPUhWdELRt7aLVyQw6VblZHioVEgQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/grid": "^3.2.11", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tabs": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.12.tgz", + "integrity": "sha512-E9O9G+wf9kaQ8UbDEDliW/oxYlJnh7oDCW1zaMOySwnG4yeCh7Wu02EOCvlQW4xvgn/i+lbEWgirf7L+yj5nRg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/textfield": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.11.0.tgz", + "integrity": "sha512-YORBgr6wlu2xfvr4MqjKFHGpj+z8LBzk14FbWDbYnnhGnv0I10pj+m2KeOHgDNFHrfkDdDOQmMIKn1UCqeUuEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tooltip": { + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.14.tgz", + "integrity": "sha512-J7CeYL2yPeKIasx1rPaEefyCHGEx2DOCx+7bM3XcKGmCxvNdVQLjimNJOt8IHlUA0nFJQOjmSW/mz9P0f2/kUw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.8.12", + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", + "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.91.1.tgz", + "integrity": "sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz", + "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.1.tgz", + "integrity": "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.91.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.90.10", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.3.tgz", + "integrity": "sha512-vCU+OTylXN3hdC8RKg68tPlBPjjxtzon7Ys46MgrSLE+JhSjSTPvoQifV6DQJeJmA8Q3KT6CphJbejupx85vFw==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz", + "integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "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/@types/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "license": "MIT" + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", + "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.263", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.263.tgz", + "integrity": "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "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==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/input-otp": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz", + "integrity": "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.18", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", + "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.4", + "tslib": "^2.8.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "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.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.0.tgz", + "integrity": "sha512-FVyCOH4IZ0eDDRycODfUqoN8ZSR2LbTvtx6RPsBgzvJ8xAXlMZNCrOFpu+jb8QbtZnpAd/cEki2pwE848pNGxw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.0.tgz", + "integrity": "sha512-Q4haR150pN/5N75O30iIsRJcr3ef7p7opFaKpcaREy0GQit6uCRu1NEiIFIwnHJQy0bsziRFBweR/5EkmHgVUQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.10.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "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.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", + "integrity": "sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sonner": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", + "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.2.1.tgz", + "integrity": "sha512-2xmhAf4UIc3PijOUcJPA1LP4AbxhpcHuHM2C26xM0k81r0maAO6uoUSHl3APmvHZcY5cZCY/bYuJdfFa4eGoaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.2.0" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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_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/vite/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/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/apps/frontend/package.json b/apps/frontend/package.json new file mode 100644 index 00000000..03b28f38 --- /dev/null +++ b/apps/frontend/package.json @@ -0,0 +1,55 @@ +{ + "name": "@playwright-reports/frontend", + "version": "5.7.4", + "description": "Playwright Reports Server Frontend", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build:vite": "vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit", + "test": "echo 'No tests configured for frontend'", + "lint": "biome check src/", + "lint:fix": "biome check src/ --write", + "format": "biome format src/ --write", + "format:check": "biome format src/", + "clean": "rm -rf dist" + }, + "dependencies": { + "@heroui/link": "2.2.12", + "@heroui/navbar": "2.2.13", + "@heroui/react": "2.7.4", + "@heroui/switch": "2.2.13", + "@heroui/system": "2.4.11", + "@heroui/theme": "2.4.11", + "@playwright-reports/shared": "*", + "@react-aria/selection": "^3.12.1", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.15.0", + "@react-aria/visually-hidden": "^3.8.28", + "@tanstack/react-query": "^5.59.15", + "@tanstack/react-query-devtools": "^5.59.15", + "clsx": "^2.1.1", + "framer-motion": "^11.11.8", + "next-themes": "^0.4.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "recharts": "^2.13.0", + "sonner": "^1.5.0", + "tailwind-merge": "^3.0.2", + "uuid": "^13.0.0" + }, + "devDependencies": { + "@types/react": "18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "10.4.20", + "postcss": "8.4.47", + "tailwind-variants": "0.2.1", + "tailwindcss": "3.4.13", + "typescript": "5.2.2", + "vite": "^6.0.3" + } +} diff --git a/postcss.config.js b/apps/frontend/postcss.config.cjs similarity index 100% rename from postcss.config.js rename to apps/frontend/postcss.config.cjs diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx new file mode 100644 index 00000000..626e17d9 --- /dev/null +++ b/apps/frontend/src/App.tsx @@ -0,0 +1,32 @@ +import { Route, Routes } from 'react-router-dom'; +import { Toaster } from 'sonner'; +import { Layout } from './components/Layout'; +import HomePage from './pages/HomePage'; +import LoginPage from './pages/LoginPage'; +import ReportDetailPage from './pages/ReportDetailPage'; +import ReportsPage from './pages/ReportsPage'; +import ResultsPage from './pages/ResultsPage'; +import SettingsPage from './pages/SettingsPage'; +import TrendsPage from './pages/TrendsPage'; +import { Providers } from './providers'; + +function App() { + return ( + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + ); +} + +export default App; diff --git a/apps/frontend/src/components/Layout.tsx b/apps/frontend/src/components/Layout.tsx new file mode 100644 index 00000000..5f82c623 --- /dev/null +++ b/apps/frontend/src/components/Layout.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from 'react'; +import { Aside } from './aside'; +import { Navbar } from './navbar'; + +interface LayoutProps { + children: ReactNode; +} + +export function Layout({ children }: LayoutProps) { + return ( +
+
+ +
+
+ +
+
+ ); +} diff --git a/app/components/aside.tsx b/apps/frontend/src/components/aside.tsx similarity index 80% rename from app/components/aside.tsx rename to apps/frontend/src/components/aside.tsx index d2a7309f..8b1c312b 100644 --- a/app/components/aside.tsx +++ b/apps/frontend/src/components/aside.tsx @@ -1,15 +1,12 @@ 'use client'; -import { Card, CardBody, Link, Badge } from '@heroui/react'; -import NextLink from 'next/link'; -import { usePathname } from 'next/navigation'; -import { useSession } from 'next-auth/react'; - +import { Badge, Card, CardBody, Link } from '@heroui/react'; +import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { siteConfig } from '../config/site'; +import { useAuth } from '../hooks/useAuth'; import { useAuthConfig } from '../hooks/useAuthConfig'; - -import { ReportIcon, ResultIcon, SettingsIcon, TrendIcon } from '@/app/components/icons'; -import { siteConfig } from '@/app/config/site'; -import useQuery from '@/app/hooks/useQuery'; +import useQuery from '../hooks/useQuery'; +import { ReportIcon, ResultIcon, SettingsIcon, TrendIcon } from './icons'; interface ServerInfo { numOfReports: number; @@ -24,18 +21,18 @@ const iconst = [ ]; export const Aside: React.FC = () => { - const pathname = usePathname(); - const session = useSession(); + const location = useLocation(); + const pathname = location.pathname; + const session = useAuth(); const { authRequired } = useAuthConfig(); + const isAuthenticated = authRequired === false || session.status === 'authenticated'; const { data: serverInfo } = useQuery('/api/info', { - dependencies: [], + enabled: isAuthenticated, }); - const isAuthenticated = authRequired === false || session.status === 'authenticated'; - return ( - +
{siteConfig.navItems.map((item) => { @@ -51,13 +48,13 @@ export const Aside: React.FC = () => { return ( diff --git a/app/components/date-format.tsx b/apps/frontend/src/components/date-format.tsx similarity index 70% rename from app/components/date-format.tsx rename to apps/frontend/src/components/date-format.tsx index c6b54a8b..57055c61 100644 --- a/app/components/date-format.tsx +++ b/apps/frontend/src/components/date-format.tsx @@ -1,11 +1,11 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; /** * Specific method for date formatting on the client * as server locale and client locale may not match */ -export default function FormattedDate({ date }: { date: Date }) { +export default function FormattedDate({ date }: Readonly<{ date: Date | string }>) { const [formattedDate, setFormattedDate] = useState(''); useEffect(() => { diff --git a/app/components/delete-report-button.tsx b/apps/frontend/src/components/delete-report-button.tsx similarity index 84% rename from app/components/delete-report-button.tsx rename to apps/frontend/src/components/delete-report-button.tsx index 6657166a..c7199a11 100644 --- a/app/components/delete-report-button.tsx +++ b/apps/frontend/src/components/delete-report-button.tsx @@ -1,12 +1,20 @@ 'use client'; -import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, useDisclosure, Button } from '@heroui/react'; +import { + Button, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + useDisclosure, +} from '@heroui/react'; import { useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; -import useMutation from '@/app/hooks/useMutation'; -import { DeleteIcon } from '@/app/components/icons'; -import { invalidateCache } from '@/app/lib/query-cache'; +import useMutation from '../hooks/useMutation'; +import { invalidateCache } from '../lib/query-cache'; +import { DeleteIcon } from './icons'; interface DeleteProjectButtonProps { reportId: string; @@ -22,7 +30,10 @@ export default function DeleteReportButton({ reportId, onDeleted }: DeleteProjec } = useMutation('/api/report/delete', { method: 'DELETE', onSuccess: () => { - invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/report' }); + invalidateCache(queryClient, { + queryKeys: ['/api/info'], + predicate: '/api/report', + }); toast.success(`report "${reportId}" deleted`); }, }); diff --git a/app/components/delete-results-button.tsx b/apps/frontend/src/components/delete-results-button.tsx similarity index 80% rename from app/components/delete-results-button.tsx rename to apps/frontend/src/components/delete-results-button.tsx index 3ed0ce85..1264641f 100644 --- a/app/components/delete-results-button.tsx +++ b/apps/frontend/src/components/delete-results-button.tsx @@ -1,12 +1,20 @@ 'use client'; -import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, useDisclosure, Button } from '@heroui/react'; +import { + Button, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + useDisclosure, +} from '@heroui/react'; import { useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; -import useMutation from '@/app/hooks/useMutation'; -import { invalidateCache } from '@/app/lib/query-cache'; -import { DeleteIcon } from '@/app/components/icons'; +import useMutation from '../hooks/useMutation'; +import { invalidateCache } from '../lib/query-cache'; +import { DeleteIcon } from './icons'; interface DeleteProjectButtonProps { resultIds: string[]; @@ -14,7 +22,11 @@ interface DeleteProjectButtonProps { label?: string; } -export default function DeleteResultsButton({ resultIds, onDeletedResult, label }: Readonly) { +export default function DeleteResultsButton({ + resultIds, + onDeletedResult, + label, +}: Readonly) { const queryClient = useQueryClient(); const { mutate: deleteResult, @@ -23,7 +35,10 @@ export default function DeleteResultsButton({ resultIds, onDeletedResult, label } = useMutation('/api/result/delete', { method: 'DELETE', onSuccess: () => { - invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/result' }); + invalidateCache(queryClient, { + queryKeys: ['/api/info'], + predicate: '/api/result', + }); toast.success(`result${resultIds.length ? '' : 's'} ${resultIds ?? 'are'} deleted`); }, }); diff --git a/app/components/generate-report-button.tsx b/apps/frontend/src/components/generate-report-button.tsx similarity index 64% rename from app/components/generate-report-button.tsx rename to apps/frontend/src/components/generate-report-button.tsx index f93e4219..ce98a2a6 100644 --- a/app/components/generate-report-button.tsx +++ b/apps/frontend/src/components/generate-report-button.tsx @@ -1,26 +1,22 @@ -'use client'; - import { + Autocomplete, + AutocompleteItem, Button, + Input, Modal, + ModalBody, ModalContent, + ModalFooter, ModalHeader, - ModalBody, useDisclosure, - ModalFooter, - Autocomplete, - AutocompleteItem, - Input, } from '@heroui/react'; -import { useEffect, useState } from 'react'; +import type { Result } from '@playwright-reports/shared'; import { useQueryClient } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; import { toast } from 'sonner'; - -import { type Result } from '../lib/storage'; - -import useQuery from '@/app/hooks/useQuery'; -import useMutation from '@/app/hooks/useMutation'; -import { invalidateCache } from '@/app/lib/query-cache'; +import useMutation from '../hooks/useMutation'; +import useQuery from '../hooks/useQuery'; +import { invalidateCache } from '../lib/query-cache'; interface DeleteProjectButtonProps { results: Result[]; @@ -39,7 +35,14 @@ export default function GenerateReportButton({ const { mutate: generateReport, isPending } = useMutation('/api/report/generate', { method: 'POST', onSuccess: (data: { reportId: string }) => { - invalidateCache(queryClient, { queryKeys: ['/api/info'], predicate: '/api/report' }); + invalidateCache(queryClient, { + queryKeys: ['/api/info'], + predicate: '/api/report', + }); + invalidateCache(queryClient, { + queryKeys: ['/api/info'], + predicate: '/api/result', + }); toast.success(`report ${data?.reportId} is generated`); setProjectName(''); setCustomName(''); @@ -48,7 +51,20 @@ export default function GenerateReportButton({ onGeneratedReport?.(); }, onError: (err: Error) => { - setGenerationError(err.message); + let errorMessage = err.message; + if ( + err.message.includes('ENOENT') || + err.message.includes('not found') || + err.message.includes('404') + ) { + errorMessage = + 'One or more selected results were not found. Please refresh the page and try again.'; + } else if (err.message.includes('ResultID not found')) { + errorMessage = + 'Some selected results no longer exist. Please refresh the page and select valid results.'; + } + + setGenerationError(errorMessage); }, }); @@ -63,7 +79,7 @@ export default function GenerateReportButton({ useEffect(() => { !projectName && setProjectName(projects?.at(0) ?? ''); - }, [projects]); + }, [projects, projectName]); const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure(); @@ -78,12 +94,28 @@ export default function GenerateReportButton({ } setGenerationError(null); - generateReport({ body: { resultsIds: results.map((r) => r.resultID), project: projectName, title: customName } }); + + const validResults = results.filter((r) => r.resultID && r.resultID.trim() !== ''); + if (validResults.length !== results.length) { + setGenerationError('Some selected results are invalid or missing IDs'); + return; + } + + const resultIds = validResults.map((r) => r.resultID); + generateReport({ + body: { resultsIds: resultIds, project: projectName, title: customName }, + }); }; return ( <> - @@ -107,16 +139,20 @@ export default function GenerateReportButton({ inputValue={projectName} isDisabled={isPending} isLoading={isResultProjectsLoading} - items={Array.from(new Set([...projects, ...(resultProjects ?? [])])).map((project) => ({ - label: project, - value: project, - }))} + items={Array.from(new Set([...projects, ...(resultProjects ?? [])])).map( + (project) => ({ + label: project, + value: project, + }) + )} label="Project name" labelPlacement="outside" placeholder="leave empty if not required" variant="bordered" onInputChange={(value) => setProjectName(value)} - onSelectionChange={(value) => value && setProjectName(value?.toString() ?? '')} + onSelectionChange={(value) => + value && setProjectName(value?.toString() ?? '') + } > {(item) => {item.label}} @@ -129,7 +165,9 @@ export default function GenerateReportButton({ placeholder="leave empty if not required" value={customName} variant="bordered" - onChange={(e: { target: { value: any } }) => setCustomName(e.target.value ?? '')} + onChange={(e: React.ChangeEvent) => + setCustomName(e.target.value ?? '') + } onClear={() => setCustomName('')} /> @@ -140,7 +178,12 @@ export default function GenerateReportButton({ Close {!generationError && ( - )} diff --git a/app/components/header-links.tsx b/apps/frontend/src/components/header-links.tsx similarity index 81% rename from app/components/header-links.tsx rename to apps/frontend/src/components/header-links.tsx index 382a1cd0..b7df1233 100644 --- a/app/components/header-links.tsx +++ b/apps/frontend/src/components/header-links.tsx @@ -1,7 +1,14 @@ import { Link } from '@heroui/link'; - -import { GithubIcon, DiscordIcon, TelegramIcon, LinkIcon, BitbucketIcon, CyborgTestIcon } from '@/app/components/icons'; -import { SiteWhiteLabelConfig } from '@/app/types'; +import type { SiteWhiteLabelConfig } from '@playwright-reports/shared'; +import { + BitbucketIcon, + CyborgTestIcon, + DiscordIcon, + GithubIcon, + LinkIcon, + SlackIcon, + TelegramIcon, +} from './icons'; interface HeaderLinksProps { config: SiteWhiteLabelConfig; @@ -17,6 +24,7 @@ export const HeaderLinks: React.FC = ({ config, withTitle = fa { name: 'github', Icon: GithubIcon, title: 'GitHub' }, { name: 'cyborgTest', Icon: CyborgTestIcon, title: 'Cyborg Test' }, { name: 'bitbucket', Icon: BitbucketIcon, title: 'Bitbucket' }, + { name: 'slack', Icon: SlackIcon, title: 'Slack' }, ]; const socialLinks = Object.entries(links).map(([name, href]) => { diff --git a/app/components/icons.tsx b/apps/frontend/src/components/icons.tsx similarity index 95% rename from app/components/icons.tsx rename to apps/frontend/src/components/icons.tsx index fe8af021..5cfdda38 100644 --- a/app/components/icons.tsx +++ b/apps/frontend/src/components/icons.tsx @@ -1,6 +1,5 @@ -import { FC } from 'react'; - -import { IconSvgProps } from '@/app/types'; +import type { IconSvgProps } from '@playwright-reports/shared'; +import type { FC } from 'react'; export const DiscordIcon: FC = ({ size = 40, width, height, ...props }) => { return ( @@ -26,6 +25,23 @@ export const GithubIcon: FC = ({ size = 40, width, height, ...prop ); }; +export const SlackIcon: FC = ({ size = 40, width, height, ...props }) => ( + + + + + + +); + export const BitbucketIcon: FC = ({ size = 40, width, height, ...props }) => { return ( = ({ size = 40, width, height, ...p export const CyborgTestIcon: FC = ({ size = 40, width, height, ...props }) => { return ( - + @@ -168,7 +190,7 @@ export const ResultIcon: FC = ({ size = 40, width, height, ...prop export const TrendIcon: FC = ({ size = 40, width, height, ...props }) => { return ( = ({ size = 40, width, height, ...props }; export const DeleteIcon: FC = (props) => ( -
- + setTicketData((prev) => ({ ...prev, summary: value }))} + onValueChange={(value) => + setTicketData((prev) => ({ ...prev, summary: value })) + } />
{ - if (editingSection === 'server') { - const newPaths = [...(tempConfig.reporterPaths || [])]; + {(editingSection === 'server' + ? tempConfig.reporterPaths || [] + : config.reporterPaths || [] + ).map((path, index) => ( +
+ { + if (editingSection === 'server') { + const newPaths = [...(tempConfig.reporterPaths || [])]; - newPaths[index] = e.target.value; - onUpdateTempConfig({ reporterPaths: newPaths }); - } - }} - /> - {editingSection === 'server' && ( - - )} -
- ), - )} + newPaths.splice(index, 1); + onUpdateTempConfig({ reporterPaths: newPaths }); + }} + > + Remove + + )} +
+ ))} {editingSection === 'server' && ( )} @@ -312,7 +333,11 @@ export default function ServerConfiguration({ )}
- {Object.entries(editingSection === 'server' ? tempConfig.headerLinks || {} : config.headerLinks || {}) + {Object.entries( + editingSection === 'server' + ? tempConfig.headerLinks || {} + : config.headerLinks || {} + ) .filter(([key]) => key !== 'cyborgTest') .map(([key, value]) => { const isDefaultLink = ['github', 'telegram', 'discord'].includes(key); @@ -320,7 +345,12 @@ export default function ServerConfiguration({ return (
- + ); })} - {Object.keys(editingSection === 'server' ? tempConfig.headerLinks || {} : config.headerLinks || {}) - .length === 0 &&

No header links configured

} + {Object.keys( + editingSection === 'server' + ? tempConfig.headerLinks || {} + : config.headerLinks || {} + ).length === 0 &&

No header links configured

}
diff --git a/app/components/stat-chart.tsx b/apps/frontend/src/components/stat-chart.tsx similarity index 90% rename from app/components/stat-chart.tsx rename to apps/frontend/src/components/stat-chart.tsx index d5766dd7..0bf4b4d6 100644 --- a/app/components/stat-chart.tsx +++ b/apps/frontend/src/components/stat-chart.tsx @@ -1,6 +1,6 @@ import { Label, Pie, PieChart } from 'recharts'; -import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from '@/app/components/ui/chart'; +import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from './ui/chart'; const chartConfig = { count: { @@ -65,7 +65,11 @@ export function StatChart({ stats }: Readonly) { if (viewBox && 'cx' in viewBox && 'cy' in viewBox) { return ( - + {`${Math.round((stats.expected / (stats.total - stats.skipped)) * 100)}%`} diff --git a/app/components/table-pagination-options.tsx b/apps/frontend/src/components/table-pagination-options.tsx similarity index 88% rename from app/components/table-pagination-options.tsx rename to apps/frontend/src/components/table-pagination-options.tsx index 32534090..b3f840bb 100644 --- a/app/components/table-pagination-options.tsx +++ b/apps/frontend/src/components/table-pagination-options.tsx @@ -1,9 +1,8 @@ +import { Input, Select, SelectItem } from '@heroui/react'; import { type ChangeEvent, useCallback } from 'react'; -import { Select, SelectItem, Input } from '@heroui/react'; - -import ProjectSelect from '@/app/components/project-select'; -import TagSelect from '@/app/components/tag-select'; -import { SearchIcon } from '@/app/components/icons'; +import { SearchIcon } from './icons'; +import ProjectSelect from './project-select'; +import TagSelect from './tag-select'; interface TablePaginationRowProps { total?: number; @@ -29,7 +28,7 @@ export default function TablePaginationOptions({ onProjectChange, onSearchChange, onTagsChange, -}: TablePaginationRowProps) { +}: Readonly) { const rowPerPageItems = rowPerPageOptions ?? defaultRowPerPageOptions; const onRowsPerPageChange = useCallback( @@ -39,7 +38,7 @@ export default function TablePaginationOptions({ setRowsPerPage(rows); setPage(1); }, - [rowsPerPage], + [setPage, setRowsPerPage] ); return ( diff --git a/app/components/tag-select.tsx b/apps/frontend/src/components/tag-select.tsx similarity index 75% rename from app/components/tag-select.tsx rename to apps/frontend/src/components/tag-select.tsx index 0e35e297..ebff8fe5 100644 --- a/app/components/tag-select.tsx +++ b/apps/frontend/src/components/tag-select.tsx @@ -1,9 +1,9 @@ 'use client'; -import { Select, SelectItem, SharedSelection } from '@heroui/react'; +import { Select, SelectItem, type SharedSelection } from '@heroui/react'; import { toast } from 'sonner'; - import useQuery from '../hooks/useQuery'; +import { buildUrl } from '../lib/url'; interface TagSelectProps { onSelect?: (tags: string[]) => void; @@ -12,12 +12,17 @@ interface TagSelectProps { project?: string; } -export default function TagSelect({ refreshId, onSelect, entity, project }: Readonly) { +export default function TagSelect({ + refreshId, + onSelect, + entity, + project, +}: Readonly) { const { data: tags, error, isLoading, - } = useQuery(`/api/${entity}/tags${project ? `?project=${encodeURIComponent(project)}` : ''}`, { + } = useQuery(buildUrl(`/api/${entity}/tags`, project ? { project } : undefined), { dependencies: [refreshId, project], }); diff --git a/app/components/theme-switch.tsx b/apps/frontend/src/components/theme-switch.tsx similarity index 81% rename from app/components/theme-switch.tsx rename to apps/frontend/src/components/theme-switch.tsx index 262745a0..6f2782dd 100644 --- a/app/components/theme-switch.tsx +++ b/apps/frontend/src/components/theme-switch.tsx @@ -1,15 +1,15 @@ 'use client'; -import { FC } from 'react'; -import { VisuallyHidden } from '@react-aria/visually-hidden'; -import { SwitchProps, useSwitch } from '@heroui/switch'; -import { useTheme } from 'next-themes'; +import { type SwitchProps, useSwitch } from '@heroui/switch'; import { useIsSSR } from '@react-aria/ssr'; +import { VisuallyHidden } from '@react-aria/visually-hidden'; import clsx from 'clsx'; +import { useTheme } from 'next-themes'; +import type { FC } from 'react'; -import { SunFilledIcon, MoonFilledIcon } from '@/app/components/icons'; +import { MoonFilledIcon, SunFilledIcon } from './icons'; -export interface ThemeSwitchProps { +interface ThemeSwitchProps { className?: string; classNames?: SwitchProps['classNames']; } @@ -34,7 +34,11 @@ export const ThemeSwitch: FC = ({ className, classNames }) => return ( @@ -55,7 +59,7 @@ export const ThemeSwitch: FC = ({ className, classNames }) => 'px-0', 'mx-0', ], - classNames?.wrapper, + classNames?.wrapper ), })} > diff --git a/app/components/trend-chart.tsx b/apps/frontend/src/components/trend-chart.tsx similarity index 82% rename from app/components/trend-chart.tsx rename to apps/frontend/src/components/trend-chart.tsx index be389803..1ed40825 100644 --- a/app/components/trend-chart.tsx +++ b/apps/frontend/src/components/trend-chart.tsx @@ -1,8 +1,8 @@ 'use client'; +import { Alert } from '@heroui/react'; +import type { ReportHistory } from '@playwright-reports/shared'; +import { Link } from 'react-router-dom'; import { Area, AreaChart, XAxis } from 'recharts'; -import Link from 'next/link'; - -import { type ReportHistory } from '@/app/lib/storage'; import { type ChartConfig, ChartContainer, @@ -10,7 +10,7 @@ import { ChartLegendContent, ChartTooltip, ChartTooltipContent, -} from '@/app/components/ui/chart'; +} from './ui/chart'; const chartConfig = { failed: { @@ -48,22 +48,26 @@ export function TrendChart({ reportHistory }: Readonly) { const chartData = reportHistory.map((r) => ({ date: new Date(r.createdAt).getTime(), - passed: getPercentage(r.stats.expected, r.stats.total), - passedCount: r.stats.expected, - failed: getPercentage(r.stats.unexpected, r.stats.total), - failedCount: r.stats.unexpected, - skipped: getPercentage(r.stats.skipped, r.stats.total), - skippedCount: r.stats.skipped, - flaky: getPercentage(r.stats.flaky, r.stats.total), - flakyCount: r.stats.flaky, - total: r.stats.total, - reportUrl: r.reportUrl, + passed: getPercentage(r.stats?.expected || 0, r.stats?.total || 0), + passedCount: r.stats?.expected || 0, + failed: getPercentage(r.stats?.unexpected || 0, r.stats?.total || 0), + failedCount: r.stats?.unexpected || 0, + skipped: getPercentage(r.stats?.skipped || 0, r.stats?.total || 0), + skippedCount: r.stats?.skipped || 0, + flaky: getPercentage(r.stats?.flaky || 0, r.stats?.total || 0), + flakyCount: r.stats?.flaky || 0, + total: r.stats?.total || 0, + reportUrl: `/report/${r.reportID}`, })); return ( {reportHistory.length <= 1 ? ( - Not enough data for trend chart +
+
+ +
+
) : ( ) { {/* Add this after the last item */} {index === 3 && ( <> - +
Total
@@ -133,7 +137,7 @@ export function TrendChart({ reportHistory }: Readonly) {
{new Date( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - item.payload.date, + item.payload.date ).toLocaleString()}
diff --git a/app/components/ui/chart.tsx b/apps/frontend/src/components/ui/chart.tsx similarity index 83% rename from app/components/ui/chart.tsx rename to apps/frontend/src/components/ui/chart.tsx index 440133f2..543e9bf5 100644 --- a/app/components/ui/chart.tsx +++ b/apps/frontend/src/components/ui/chart.tsx @@ -1,9 +1,17 @@ 'use client'; -import { ReactNode, ComponentType, createContext, useContext, forwardRef, useId, useMemo } from 'react'; +import { + type ComponentType, + createContext, + forwardRef, + type ReactNode, + useContext, + useId, + useMemo, +} from 'react'; import * as RechartsPrimitive from 'recharts'; -import { cn } from '@/app/lib/tailwind'; +import { cn } from '../../styles/globals'; const THEMES = { light: '', dark: '.dark' } as const; @@ -11,7 +19,10 @@ export type ChartConfig = { [k in string]: { label?: ReactNode; icon?: ComponentType; - } & ({ color?: string; theme?: never } | { color?: never; theme: Record }); + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); }; type ChartContextProps = { @@ -46,7 +57,7 @@ const ChartContainer = forwardRef< ref={ref} className={cn( "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none", - className, + className )} data-chart={chartId} {...props} @@ -69,6 +80,7 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { return (