From f3ac595d06b1655c52a630b44a7350f44de79872 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Wed, 11 Feb 2026 20:42:01 +0100 Subject: [PATCH] MCP Server 2.0 (#452) * MCP Server 2.0 - Updated application version from 2.23.14 to 2.24.0 in package.json. - MCP Server improved with new features - Migrated functionality from Dev Tools to MCP Server - Improved getMonitoringProvider not to crash application when misconfigured --- .claude/commands/feature-builder.md | 10 +- .claude/skills/server-action-builder/SKILL.md | 36 +- .claude/skills/service-builder/SKILL.md | 308 + .claude/skills/service-builder/examples.md | 273 + .gitignore | 5 +- .mcp.json | 4 +- CLAUDE.md | 2 +- .../_components/database-tools-interface.tsx | 0 .../database/_components/enum-browser.tsx | 0 .../database/_components/function-browser.tsx | 0 .../database/_components/schema-explorer.tsx | 0 .../database/_components/table-browser.tsx | 0 .../_lib/server/database-tools.loader.ts | 0 .../_lib/server/table-server-actions.ts | 0 .../app/{mcp-server => }/database/page.tsx | 6 +- apps/dev-tool/app/emails/[id]/page.tsx | 41 +- apps/dev-tool/app/emails/lib/email-loader.tsx | 60 - .../dev-tool/app/emails/lib/server-actions.ts | 11 +- apps/dev-tool/app/emails/page.tsx | 102 +- apps/dev-tool/app/lib/connectivity-service.ts | 228 - .../app/lib/prerequisites-dashboard.loader.ts | 154 + .../app/lib/status-dashboard.loader.ts | 116 + .../_components/mcp-server-interface.tsx | 13 - .../_components/mcp-server-tabs.tsx | 53 - .../_components/prd-manager-client.tsx | 188 - apps/dev-tool/app/mcp-server/page.tsx | 106 - apps/dev-tool/app/page.tsx | 187 +- .../_components/prd-detail-view.tsx | 4 +- .../{mcp-server => }/prds/[filename]/page.tsx | 2 +- .../prds/_components/prds-list-interface.tsx | 2 +- .../_components/user-story-display.tsx | 0 .../_lib/schemas/create-prd.schema.ts | 0 .../_lib/server/prd-loader.ts | 0 .../_lib/server/prd-page.loader.ts | 0 .../app/{mcp-server => }/prds/page.tsx | 2 +- .../components/translations-comparison.tsx | 59 +- .../app/translations/lib/server-actions.ts | 42 +- .../translations/lib/translations-loader.ts | 59 +- apps/dev-tool/app/translations/page.tsx | 1 - .../dev-tool/app/variables/lib/env-scanner.ts | 477 +- .../app/variables/lib/env-variables-model.ts | 1341 +---- .../app/variables/lib/server-actions.ts | 78 +- apps/dev-tool/app/variables/page.tsx | 31 +- apps/dev-tool/components/app-sidebar.tsx | 23 +- apps/dev-tool/components/status-tile.tsx | 7 +- apps/e2e/package.json | 2 +- apps/web/AGENTS.md | 1 + .../app/(marketing)/changelog/[slug]/page.tsx | 8 +- apps/web/app/admin/AGENTS.md | 1 + apps/web/components/error-page-content.tsx | 4 +- apps/web/next.config.mjs | 2 +- package.json | 4 +- packages/email-templates/AGENTS.md | 22 + packages/email-templates/CLAUDE.md | 1 + packages/email-templates/package.json | 8 +- packages/email-templates/src/registry.ts | 39 + packages/i18n/src/create-i18n-settings.ts | 1 + packages/i18n/src/i18n.client.ts | 1 + packages/mcp-server/AGENTS.md | 16 + packages/mcp-server/CLAUDE.md | 1 + packages/mcp-server/package.json | 37 +- packages/mcp-server/src/index.ts | 20 + packages/mcp-server/src/tools/components.ts | 47 +- packages/mcp-server/src/tools/database.ts | 126 +- .../tools/db/__tests__/kit-db.service.test.ts | 248 + packages/mcp-server/src/tools/db/index.ts | 365 ++ .../mcp-server/src/tools/db/kit-db.service.ts | 505 ++ packages/mcp-server/src/tools/db/schema.ts | 53 + .../deps-upgrade-advisor.service.test.ts | 99 + .../deps-upgrade-advisor.tool.test.ts | 50 + .../deps-upgrade-advisor.service.ts | 307 + .../src/tools/deps-upgrade-advisor/index.ts | 122 + .../src/tools/deps-upgrade-advisor/schema.ts | 52 + .../dev/__tests__/kit-dev.service.test.ts | 1016 ++++ packages/mcp-server/src/tools/dev/index.ts | 494 ++ .../src/tools/dev/kit-dev.service.ts | 723 +++ packages/mcp-server/src/tools/dev/schema.ts | 69 + .../__tests__/kit-emails.service.test.ts | 292 + packages/mcp-server/src/tools/emails/index.ts | 109 + .../src/tools/emails/kit-emails.service.ts | 289 + .../mcp-server/src/tools/emails/schema.ts | 46 + .../env/__tests__/kit-env.service.test.ts | 845 +++ packages/mcp-server/src/tools/env/index.ts | 177 + .../src/tools/env/kit-env.service.ts | 320 + packages/mcp-server/src/tools/env/model.ts | 1430 +++++ .../mcp-server/src/tools/env/public-api.ts | 9 + packages/mcp-server/src/tools/env/scanner.ts | 480 ++ packages/mcp-server/src/tools/env/schema.ts | 102 + packages/mcp-server/src/tools/env/types.ts | 54 + .../__tests__/kit-mailbox.service.test.ts | 190 + .../mcp-server/src/tools/mailbox/index.ts | 194 + .../src/tools/mailbox/kit-mailbox.service.ts | 261 + .../mcp-server/src/tools/mailbox/schema.ts | 79 + packages/mcp-server/src/tools/migrations.ts | 41 +- packages/mcp-server/src/tools/prd-manager.ts | 526 +- .../kit-prerequisites.service.test.ts | 109 + .../__tests__/kit-prerequisites.tool.test.ts | 37 + .../src/tools/prerequisites/index.ts | 191 + .../kit-prerequisites.service.ts | 405 ++ .../src/tools/prerequisites/schema.ts | 30 + packages/mcp-server/src/tools/prompts.ts | 10 +- .../__tests__/run-checks.service.test.ts | 108 + .../__tests__/run-checks.tool.test.ts | 36 + .../mcp-server/src/tools/run-checks/index.ts | 115 + .../tools/run-checks/run-checks.service.ts | 147 + .../mcp-server/src/tools/run-checks/schema.ts | 40 + packages/mcp-server/src/tools/scripts.ts | 28 +- .../__tests__/kit-status.service.test.ts | 431 ++ packages/mcp-server/src/tools/status/index.ts | 144 + .../src/tools/status/kit-status.service.ts | 549 ++ .../mcp-server/src/tools/status/schema.ts | 48 + .../kit-translations.service.test.ts | 466 ++ .../src/tools/translations/index.ts | 219 + .../translations/kit-translations.service.ts | 535 ++ .../src/tools/translations/schema.ts | 183 + packages/mcp-server/tsconfig.json | 7 +- packages/mcp-server/tsup.config.ts | 11 + packages/mcp-server/vitest.config.ts | 8 + .../api/src/get-monitoring-provider.ts | 18 +- packages/shared/package.json | 3 +- packages/ui/package.json | 3 +- pnpm-lock.yaml | 5252 ++++++++++------- pnpm-workspace.yaml | 16 +- 123 files changed, 17803 insertions(+), 5265 deletions(-) create mode 100644 .claude/skills/service-builder/SKILL.md create mode 100644 .claude/skills/service-builder/examples.md rename apps/dev-tool/app/{mcp-server => }/database/_components/database-tools-interface.tsx (100%) rename apps/dev-tool/app/{mcp-server => }/database/_components/enum-browser.tsx (100%) rename apps/dev-tool/app/{mcp-server => }/database/_components/function-browser.tsx (100%) rename apps/dev-tool/app/{mcp-server => }/database/_components/schema-explorer.tsx (100%) rename apps/dev-tool/app/{mcp-server => }/database/_components/table-browser.tsx (100%) rename apps/dev-tool/app/{mcp-server => }/database/_lib/server/database-tools.loader.ts (100%) rename apps/dev-tool/app/{mcp-server => }/database/_lib/server/table-server-actions.ts (100%) rename apps/dev-tool/app/{mcp-server => }/database/page.tsx (89%) delete mode 100644 apps/dev-tool/app/emails/lib/email-loader.tsx delete mode 100644 apps/dev-tool/app/lib/connectivity-service.ts create mode 100644 apps/dev-tool/app/lib/prerequisites-dashboard.loader.ts create mode 100644 apps/dev-tool/app/lib/status-dashboard.loader.ts delete mode 100644 apps/dev-tool/app/mcp-server/_components/mcp-server-interface.tsx delete mode 100644 apps/dev-tool/app/mcp-server/_components/mcp-server-tabs.tsx delete mode 100644 apps/dev-tool/app/mcp-server/_components/prd-manager-client.tsx delete mode 100644 apps/dev-tool/app/mcp-server/page.tsx rename apps/dev-tool/app/{mcp-server => }/prds/[filename]/_components/prd-detail-view.tsx (98%) rename apps/dev-tool/app/{mcp-server => }/prds/[filename]/page.tsx (92%) rename apps/dev-tool/app/{mcp-server => }/prds/_components/prds-list-interface.tsx (98%) rename apps/dev-tool/app/{mcp-server => prds}/_components/user-story-display.tsx (100%) rename apps/dev-tool/app/{mcp-server => prds}/_lib/schemas/create-prd.schema.ts (100%) rename apps/dev-tool/app/{mcp-server => prds}/_lib/server/prd-loader.ts (100%) rename apps/dev-tool/app/{mcp-server => prds}/_lib/server/prd-page.loader.ts (100%) rename apps/dev-tool/app/{mcp-server => }/prds/page.tsx (88%) create mode 100644 packages/email-templates/AGENTS.md create mode 100644 packages/email-templates/CLAUDE.md create mode 100644 packages/email-templates/src/registry.ts create mode 100644 packages/mcp-server/AGENTS.md create mode 100644 packages/mcp-server/CLAUDE.md create mode 100644 packages/mcp-server/src/tools/db/__tests__/kit-db.service.test.ts create mode 100644 packages/mcp-server/src/tools/db/index.ts create mode 100644 packages/mcp-server/src/tools/db/kit-db.service.ts create mode 100644 packages/mcp-server/src/tools/db/schema.ts create mode 100644 packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.service.test.ts create mode 100644 packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.tool.test.ts create mode 100644 packages/mcp-server/src/tools/deps-upgrade-advisor/deps-upgrade-advisor.service.ts create mode 100644 packages/mcp-server/src/tools/deps-upgrade-advisor/index.ts create mode 100644 packages/mcp-server/src/tools/deps-upgrade-advisor/schema.ts create mode 100644 packages/mcp-server/src/tools/dev/__tests__/kit-dev.service.test.ts create mode 100644 packages/mcp-server/src/tools/dev/index.ts create mode 100644 packages/mcp-server/src/tools/dev/kit-dev.service.ts create mode 100644 packages/mcp-server/src/tools/dev/schema.ts create mode 100644 packages/mcp-server/src/tools/emails/__tests__/kit-emails.service.test.ts create mode 100644 packages/mcp-server/src/tools/emails/index.ts create mode 100644 packages/mcp-server/src/tools/emails/kit-emails.service.ts create mode 100644 packages/mcp-server/src/tools/emails/schema.ts create mode 100644 packages/mcp-server/src/tools/env/__tests__/kit-env.service.test.ts create mode 100644 packages/mcp-server/src/tools/env/index.ts create mode 100644 packages/mcp-server/src/tools/env/kit-env.service.ts create mode 100644 packages/mcp-server/src/tools/env/model.ts create mode 100644 packages/mcp-server/src/tools/env/public-api.ts create mode 100644 packages/mcp-server/src/tools/env/scanner.ts create mode 100644 packages/mcp-server/src/tools/env/schema.ts create mode 100644 packages/mcp-server/src/tools/env/types.ts create mode 100644 packages/mcp-server/src/tools/mailbox/__tests__/kit-mailbox.service.test.ts create mode 100644 packages/mcp-server/src/tools/mailbox/index.ts create mode 100644 packages/mcp-server/src/tools/mailbox/kit-mailbox.service.ts create mode 100644 packages/mcp-server/src/tools/mailbox/schema.ts create mode 100644 packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.service.test.ts create mode 100644 packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.tool.test.ts create mode 100644 packages/mcp-server/src/tools/prerequisites/index.ts create mode 100644 packages/mcp-server/src/tools/prerequisites/kit-prerequisites.service.ts create mode 100644 packages/mcp-server/src/tools/prerequisites/schema.ts create mode 100644 packages/mcp-server/src/tools/run-checks/__tests__/run-checks.service.test.ts create mode 100644 packages/mcp-server/src/tools/run-checks/__tests__/run-checks.tool.test.ts create mode 100644 packages/mcp-server/src/tools/run-checks/index.ts create mode 100644 packages/mcp-server/src/tools/run-checks/run-checks.service.ts create mode 100644 packages/mcp-server/src/tools/run-checks/schema.ts create mode 100644 packages/mcp-server/src/tools/status/__tests__/kit-status.service.test.ts create mode 100644 packages/mcp-server/src/tools/status/index.ts create mode 100644 packages/mcp-server/src/tools/status/kit-status.service.ts create mode 100644 packages/mcp-server/src/tools/status/schema.ts create mode 100644 packages/mcp-server/src/tools/translations/__tests__/kit-translations.service.test.ts create mode 100644 packages/mcp-server/src/tools/translations/index.ts create mode 100644 packages/mcp-server/src/tools/translations/kit-translations.service.ts create mode 100644 packages/mcp-server/src/tools/translations/schema.ts create mode 100644 packages/mcp-server/tsup.config.ts create mode 100644 packages/mcp-server/vitest.config.ts diff --git a/.claude/commands/feature-builder.md b/.claude/commands/feature-builder.md index 7e0f80924..47f1ee7fe 100644 --- a/.claude/commands/feature-builder.md +++ b/.claude/commands/feature-builder.md @@ -55,11 +55,13 @@ create policy "projects_write" on public.projects for all Use `server-action-builder` skill for detailed patterns. +**Rule: Services are decoupled from interfaces.** The service is pure logic that receives dependencies (database client, etc.) as arguments — it never imports framework-specific modules. The server action is a thin adapter that resolves dependencies and calls the service. This means the same service can be called from a server action, an MCP tool, a CLI command, or a unit test with zero changes. + Create in route's `_lib/server/` directory: 1. **Schema** (`_lib/schemas/feature.schema.ts`) -2. **Service** (`_lib/server/feature.service.ts`) -3. **Actions** (`_lib/server/server-actions.ts`) +2. **Service** (`_lib/server/feature.service.ts`) — pure logic, dependencies injected, testable in isolation +3. **Actions** (`_lib/server/server-actions.ts`) — thin adapter, no business logic ### Phase 3: UI Components @@ -148,7 +150,9 @@ apps/web/app/home/[account]/projects/ ### Server Layer - [ ] Zod schema in `_lib/schemas/` -- [ ] Service class in `_lib/server/` +- [ ] Service class in `_lib/server/` with dependencies injected (not imported) +- [ ] Service contains all business logic — testable with mock dependencies +- [ ] Server actions are thin adapters — resolve dependencies, call service, handle revalidation - [ ] Server actions use `enhanceAction` - [ ] Actions have `auth: true` and `schema` options - [ ] Logging added for operations diff --git a/.claude/skills/server-action-builder/SKILL.md b/.claude/skills/server-action-builder/SKILL.md index 0ade7a840..a6732816d 100644 --- a/.claude/skills/server-action-builder/SKILL.md +++ b/.claude/skills/server-action-builder/SKILL.md @@ -29,22 +29,24 @@ export type CreateFeatureInput = z.infer; ### Step 2: Create Service Layer +**North star: services are decoupled from their interface.** The service is pure logic — it receives a database client as a dependency, never imports one. This means the same service works whether called from a server action, an MCP tool, a CLI command, or a plain unit test. + Create service in `_lib/server/`: ```typescript // _lib/server/feature.service.ts -import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import type { SupabaseClient } from '@supabase/supabase-js'; import type { CreateFeatureInput } from '../schemas/feature.schema'; -export function createFeatureService() { - return new FeatureService(); +export function createFeatureService(client: SupabaseClient) { + return new FeatureService(client); } class FeatureService { - async create(data: CreateFeatureInput) { - const client = getSupabaseServerClient(); + constructor(private readonly client: SupabaseClient) {} - const { data: result, error } = await client + async create(data: CreateFeatureInput) { + const { data: result, error } = await this.client .from('features') .insert({ name: data.name, @@ -60,7 +62,11 @@ class FeatureService { } ``` -### Step 3: Create Server Action +The service never calls `getSupabaseServerClient()` — the caller provides the client. This keeps the service testable (pass a mock client) and reusable (any interface can supply its own client). + +### Step 3: Create Server Action (Thin Adapter) + +The action is a **thin adapter** — it resolves dependencies (client, logger) and delegates to the service. No business logic lives here. Create action in `_lib/server/server-actions.ts`: @@ -69,6 +75,7 @@ Create action in `_lib/server/server-actions.ts`: import { enhanceAction } from '@kit/next/actions'; import { getLogger } from '@kit/shared/logger'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { revalidatePath } from 'next/cache'; import { CreateFeatureSchema } from '../schemas/feature.schema'; @@ -81,7 +88,8 @@ export const createFeatureAction = enhanceAction( logger.info(ctx, 'Creating feature'); - const service = createFeatureService(); + const client = getSupabaseServerClient(); + const service = createFeatureService(client); const result = await service.create(data); logger.info({ ...ctx, featureId: result.id }, 'Feature created'); @@ -99,11 +107,13 @@ export const createFeatureAction = enhanceAction( ## Key Patterns -1. **Schema in separate file** - Reusable between client and server -2. **Service layer** - Business logic isolated from action -3. **Logging** - Always log before and after operations -4. **Revalidation** - Use `revalidatePath` after mutations -5. **Trust RLS** - Don't add manual auth checks (RLS handles it) +1. **Services are pure, interfaces are thin adapters.** The service contains all business logic. The server action (or MCP tool, or CLI command) is glue code that resolves dependencies and calls the service. If an MCP tool and a server action do the same thing, they call the same service function. +2. **Inject dependencies, don't import them in services.** Services receive their database client, logger, or any I/O capability as constructor arguments — never by importing framework-specific modules. This keeps them testable with stubs and reusable across interfaces. +3. **Schema in separate file** - Reusable between client and server +4. **Logging** - Always log before and after operations +5. **Revalidation** - Use `revalidatePath` after mutations +6. **Trust RLS** - Don't add manual auth checks (RLS handles it) +7. **Testable in isolation** - Because services accept their dependencies, you can test them with a mock client and no running infrastructure ## File Structure diff --git a/.claude/skills/service-builder/SKILL.md b/.claude/skills/service-builder/SKILL.md new file mode 100644 index 000000000..f27daf8be --- /dev/null +++ b/.claude/skills/service-builder/SKILL.md @@ -0,0 +1,308 @@ +--- +name: service-builder +description: Build pure, interface-agnostic services with injected dependencies. Use when creating business logic that must work across server actions, MCP tools, CLI commands, or tests. Invoke with /service-builder. +--- + +# Service Builder + +You are an expert at building pure, testable services that are decoupled from their callers. + +## North Star + +**Every service is decoupled from its interface (I/O).** A service takes plain data in, does work, and returns plain data out. It has no knowledge of whether it was called from an MCP tool, a server action, a CLI command, a route handler, or a test. The caller is a thin adapter that resolves dependencies and delegates. + +## Workflow + +When asked to create a service, follow these steps: + +### Step 1: Define the Contract + +Start with the input/output types. These are plain TypeScript — no framework types. + +```typescript +// _lib/schemas/project.schema.ts +import { z } from 'zod'; + +export const CreateProjectSchema = z.object({ + name: z.string().min(1), + accountId: z.string().uuid(), +}); + +export type CreateProjectInput = z.infer; + +export interface Project { + id: string; + name: string; + account_id: string; + created_at: string; +} +``` + +### Step 2: Build the Service + +The service receives all dependencies through its constructor. It never imports framework-specific modules (`getSupabaseServerClient`, `getLogger`, `revalidatePath`, etc.). + +```typescript +// _lib/server/project.service.ts +import type { SupabaseClient } from '@supabase/supabase-js'; + +import type { CreateProjectInput, Project } from '../schemas/project.schema'; + +export function createProjectService(client: SupabaseClient) { + return new ProjectService(client); +} + +class ProjectService { + constructor(private readonly client: SupabaseClient) {} + + async create(data: CreateProjectInput): Promise { + const { data: result, error } = await this.client + .from('projects') + .insert({ + name: data.name, + account_id: data.accountId, + }) + .select() + .single(); + + if (error) throw error; + + return result; + } + + async list(accountId: string): Promise { + const { data, error } = await this.client + .from('projects') + .select('*') + .eq('account_id', accountId) + .order('created_at', { ascending: false }); + + if (error) throw error; + + return data; + } + + async delete(projectId: string): Promise { + const { error } = await this.client + .from('projects') + .delete() + .eq('id', projectId); + + if (error) throw error; + } +} +``` + +### Step 3: Write Thin Adapters + +Each interface is a thin adapter — it resolves dependencies, calls the service, and handles interface-specific concerns (revalidation, redirects, MCP formatting, CLI output). + +**Server Action adapter:** + +```typescript +// _lib/server/server-actions.ts +'use server'; + +import { enhanceAction } from '@kit/next/actions'; +import { getLogger } from '@kit/shared/logger'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { revalidatePath } from 'next/cache'; + +import { CreateProjectSchema } from '../schemas/project.schema'; +import { createProjectService } from './project.service'; + +export const createProjectAction = enhanceAction( + async function (data, user) { + const logger = await getLogger(); + logger.info({ name: 'create-project', userId: user.id }, 'Creating project'); + + const client = getSupabaseServerClient(); + const service = createProjectService(client); + const result = await service.create(data); + + revalidatePath('/home/[account]/projects'); + + return { success: true, data: result }; + }, + { + auth: true, + schema: CreateProjectSchema, + }, +); +``` + +**Route Handler adapter:** + +```typescript +// app/api/projects/route.ts +import { enhanceRouteHandler } from '@kit/next/routes'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { NextResponse } from 'next/server'; + +import { CreateProjectSchema } from '../_lib/schemas/project.schema'; +import { createProjectService } from '../_lib/server/project.service'; + +export const POST = enhanceRouteHandler( + async function ({ body, user }) { + const client = getSupabaseServerClient(); + const service = createProjectService(client); + const result = await service.create(body); + + return NextResponse.json(result); + }, + { + auth: true, + schema: CreateProjectSchema, + }, +); +``` + +**MCP Tool adapter:** + +```typescript +// mcp/tools/kit_project_create.ts +import { createProjectService } from '../../_lib/server/project.service'; + +export const kit_project_create: McpToolHandler = async (input, context) => { + const client = context.getSupabaseClient(); + const service = createProjectService(client); + + return service.create(input); +}; +``` + +### Step 4: Write Tests + +Because the service accepts dependencies, you can test it with stubs — no running database, no framework runtime. + +```typescript +// _lib/server/__tests__/project.service.test.ts +import { describe, it, expect, vi } from 'vitest'; + +import { createProjectService } from '../project.service'; + +function createMockClient(overrides: Record = {}) { + const mockChain = { + insert: vi.fn().mockReturnThis(), + select: vi.fn().mockReturnThis(), + single: vi.fn().mockResolvedValue({ + data: { id: 'proj-1', name: 'Test', account_id: 'acc-1', created_at: new Date().toISOString() }, + error: null, + }), + delete: vi.fn().mockReturnThis(), + eq: vi.fn().mockReturnThis(), + order: vi.fn().mockResolvedValue({ data: [], error: null }), + ...overrides, + }; + + return { + from: vi.fn(() => mockChain), + mockChain, + } as unknown as SupabaseClient; +} + +describe('ProjectService', () => { + it('creates a project', async () => { + const client = createMockClient(); + const service = createProjectService(client); + + const result = await service.create({ + name: 'Test Project', + accountId: 'acc-1', + }); + + expect(result.id).toBe('proj-1'); + expect(client.from).toHaveBeenCalledWith('projects'); + }); + + it('throws on database error', async () => { + const client = createMockClient({ + single: vi.fn().mockResolvedValue({ + data: null, + error: { message: 'unique violation' }, + }), + }); + + const service = createProjectService(client); + + await expect( + service.create({ name: 'Dup', accountId: 'acc-1' }), + ).rejects.toEqual({ message: 'unique violation' }); + }); +}); +``` + +## Rules + +1. **Services are pure functions over data.** Plain objects/primitives in, plain objects/primitives out. No `Request`/`Response`, no MCP context, no `FormData`. + +2. **Inject dependencies, never import them.** The service receives its database client, storage client, or any I/O capability as a constructor argument. Never call `getSupabaseServerClient()` inside a service. + +3. **Adapters are trivial glue.** A server action resolves the client, calls the service, and handles `revalidatePath`. An MCP tool resolves the client, calls the service, and formats the response. No business logic in adapters. + +4. **One service, many callers.** If two interfaces do the same thing, they call the same service function. Duplicating logic is a violation. + +5. **Testable in isolation.** Pass a mock client, assert the output. If you need a running database to test a service, refactor until you don't. + +## What Goes Where + +| Concern | Location | Example | +|---------|----------|---------| +| Input validation (Zod) | `_lib/schemas/` | `CreateProjectSchema` | +| Business logic | `_lib/server/*.service.ts` | `ProjectService.create()` | +| Auth check | Adapter (`enhanceAction({ auth: true })`) | Server action wrapper | +| Logging | Adapter | `logger.info()` before/after service call | +| Cache revalidation | Adapter | `revalidatePath()` after mutation | +| Redirect | Adapter | `redirect()` after creation | +| MCP response format | Adapter | Return service result as MCP content | + +## File Structure + +``` +feature/ +├── _lib/ +│ ├── schemas/ +│ │ └── feature.schema.ts # Zod schemas + TS types +│ └── server/ +│ ├── feature.service.ts # Pure service (dependencies injected) +│ ├── server-actions.ts # Server action adapters +│ └── __tests__/ +│ └── feature.service.test.ts # Unit tests with mock client +└── _components/ + └── feature-form.tsx +``` + +## Anti-Patterns + +```typescript +// ❌ BAD: Service imports framework-specific client +class ProjectService { + async create(data: CreateProjectInput) { + const client = getSupabaseServerClient(); // coupling! + // ... + } +} + +// ❌ BAD: Business logic in the adapter +export const createProjectAction = enhanceAction( + async function (data, user) { + const client = getSupabaseServerClient(); + // Business logic directly in the action — not reusable + if (data.name.length > 100) throw new Error('Name too long'); + const { data: result } = await client.from('projects').insert(data); + return result; + }, + { auth: true, schema: CreateProjectSchema }, +); + +// ❌ BAD: Two interfaces duplicate the same logic +// server-actions.ts +const result = await client.from('projects').insert(...).select().single(); +// mcp-tool.ts +const result = await client.from('projects').insert(...).select().single(); +// Should be: both call projectService.create() +``` + +## Reference + +See `[Examples](examples.md)` for more patterns including services with multiple dependencies, services that compose other services, and testing strategies. diff --git a/.claude/skills/service-builder/examples.md b/.claude/skills/service-builder/examples.md new file mode 100644 index 000000000..7f5f09c69 --- /dev/null +++ b/.claude/skills/service-builder/examples.md @@ -0,0 +1,273 @@ +# Service Builder Examples + +## Service with Multiple Dependencies + +When a service needs more than just a database client, inject all dependencies. + +```typescript +// _lib/server/invoice.service.ts +import type { SupabaseClient } from '@supabase/supabase-js'; + +interface InvoiceServiceDeps { + client: SupabaseClient; + storage: SupabaseClient['storage']; +} + +export function createInvoiceService(deps: InvoiceServiceDeps) { + return new InvoiceService(deps); +} + +class InvoiceService { + constructor(private readonly deps: InvoiceServiceDeps) {} + + async generatePdf(invoiceId: string): Promise<{ url: string }> { + const { data: invoice, error } = await this.deps.client + .from('invoices') + .select('*') + .eq('id', invoiceId) + .single(); + + if (error) throw error; + + const pdf = this.buildPdf(invoice); + + const { data: upload, error: uploadError } = await this.deps.storage + .from('invoices') + .upload(`${invoiceId}.pdf`, pdf); + + if (uploadError) throw uploadError; + + return { url: upload.path }; + } + + private buildPdf(invoice: Record): Uint8Array { + // Pure logic — no I/O + // ... + return new Uint8Array(); + } +} +``` + +**Server action adapter:** + +```typescript +'use server'; + +import { enhanceAction } from '@kit/next/actions'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; + +import { GenerateInvoiceSchema } from '../schemas/invoice.schema'; +import { createInvoiceService } from './invoice.service'; + +export const generateInvoicePdfAction = enhanceAction( + async function (data, user) { + const client = getSupabaseServerClient(); + const service = createInvoiceService({ + client, + storage: client.storage, + }); + + return service.generatePdf(data.invoiceId); + }, + { auth: true, schema: GenerateInvoiceSchema }, +); +``` + +## Service Composing Other Services + +Services can depend on other services — compose at the adapter level. + +```typescript +// _lib/server/onboarding.service.ts +import type { ProjectService } from './project.service'; +import type { NotificationService } from './notification.service'; + +interface OnboardingServiceDeps { + projects: ProjectService; + notifications: NotificationService; +} + +export function createOnboardingService(deps: OnboardingServiceDeps) { + return new OnboardingService(deps); +} + +class OnboardingService { + constructor(private readonly deps: OnboardingServiceDeps) {} + + async onboardAccount(params: { accountId: string; accountName: string }) { + // Create default project + const project = await this.deps.projects.create({ + name: `${params.accountName}'s First Project`, + accountId: params.accountId, + }); + + // Send welcome notification + await this.deps.notifications.send({ + accountId: params.accountId, + type: 'welcome', + data: { projectId: project.id }, + }); + + return { project }; + } +} +``` + +**Adapter composes the dependency tree:** + +```typescript +'use server'; + +import { getSupabaseServerClient } from '@kit/supabase/server-client'; + +import { createOnboardingService } from './onboarding.service'; +import { createProjectService } from './project.service'; +import { createNotificationService } from './notification.service'; + +export const onboardAccountAction = enhanceAction( + async function (data, user) { + const client = getSupabaseServerClient(); + + const service = createOnboardingService({ + projects: createProjectService(client), + notifications: createNotificationService(client), + }); + + return service.onboardAccount(data); + }, + { auth: true, schema: OnboardAccountSchema }, +); +``` + +## Pure Validation Service (No I/O) + +Some services are entirely pure — they don't even need a database client. + +```typescript +// _lib/server/pricing.service.ts + +interface PricingInput { + plan: 'starter' | 'pro' | 'enterprise'; + seats: number; + billingPeriod: 'monthly' | 'yearly'; +} + +interface PricingResult { + unitPrice: number; + total: number; + discount: number; + currency: string; +} + +export function calculatePricing(input: PricingInput): PricingResult { + const basePrices = { starter: 900, pro: 2900, enterprise: 9900 }; + const unitPrice = basePrices[input.plan]; + const yearlyDiscount = input.billingPeriod === 'yearly' ? 0.2 : 0; + const seatDiscount = input.seats >= 10 ? 0.1 : 0; + const discount = Math.min(yearlyDiscount + seatDiscount, 0.3); + const total = Math.round(unitPrice * input.seats * (1 - discount)); + + return { unitPrice, total, discount, currency: 'usd' }; +} +``` + +This is the simplest case — a plain function, no class, no dependencies. Trivially testable: + +```typescript +import { calculatePricing } from '../pricing.service'; + +it('applies yearly discount', () => { + const result = calculatePricing({ + plan: 'pro', + seats: 1, + billingPeriod: 'yearly', + }); + + expect(result.discount).toBe(0.2); + expect(result.total).toBe(2320); // 2900 * 0.8 +}); +``` + +## Testing with Mock Client + +Full mock pattern for Supabase client: + +```typescript +import { vi } from 'vitest'; +import type { SupabaseClient } from '@supabase/supabase-js'; + +/** + * Creates a chainable mock that mimics Supabase's query builder. + * Override any method in the chain via the `overrides` param. + */ +export function createMockSupabaseClient( + resolvedValue: { data: unknown; error: unknown } = { data: null, error: null }, + overrides: Record = {}, +) { + const chain: Record> = {}; + + // Every method returns `this` (chainable) by default + const methods = [ + 'select', 'insert', 'update', 'upsert', 'delete', + 'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', + 'like', 'ilike', 'is', 'order', 'limit', 'range', + 'single', 'maybeSingle', + ]; + + for (const method of methods) { + chain[method] = vi.fn().mockReturnThis(); + } + + // Terminal methods resolve with data + chain.single = vi.fn().mockResolvedValue(resolvedValue); + chain.maybeSingle = vi.fn().mockResolvedValue(resolvedValue); + + // Apply overrides + for (const [key, value] of Object.entries(overrides)) { + chain[key] = vi.fn().mockImplementation( + typeof value === 'function' ? value : () => value, + ); + } + + // Non-terminal chains that don't end with single/maybeSingle + // resolve when awaited via .then() + const proxyHandler: ProxyHandler = { + get(target, prop) { + if (prop === 'then') { + return (resolve: (v: unknown) => void) => resolve(resolvedValue); + } + return target[prop as string] ?? vi.fn().mockReturnValue(target); + }, + }; + + const chainProxy = new Proxy(chain, proxyHandler); + + return { + from: vi.fn(() => chainProxy), + chain, + } as unknown as SupabaseClient & { chain: typeof chain }; +} +``` + +Usage: + +```typescript +import { createMockSupabaseClient } from '../test-utils'; +import { createProjectService } from '../project.service'; + +it('lists projects for an account', async () => { + const projects = [ + { id: '1', name: 'Alpha', account_id: 'acc-1' }, + { id: '2', name: 'Beta', account_id: 'acc-1' }, + ]; + + const client = createMockSupabaseClient({ data: projects, error: null }); + const service = createProjectService(client); + + const result = await service.list('acc-1'); + + expect(result).toEqual(projects); + expect(client.from).toHaveBeenCalledWith('projects'); + expect(client.chain.eq).toHaveBeenCalledWith('account_id', 'acc-1'); +}); +``` diff --git a/.gitignore b/.gitignore index 105756b57..3c3c8d420 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,7 @@ yarn-error.log* .contentlayer/ # ts-node cache -node-compile-cache/ \ No newline at end of file +node-compile-cache/ + +# prds +.prds/ \ No newline at end of file diff --git a/.mcp.json b/.mcp.json index 6981e9a5b..43e5b5ca2 100644 --- a/.mcp.json +++ b/.mcp.json @@ -3,7 +3,7 @@ "makerkit": { "type": "stdio", "command": "node", - "args": ["packages/mcp-server/build/index.js"] + "args": ["packages/mcp-server/build/index.cjs"] } } -} +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 43c994c2d..eef4bd20c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -@AGENTS.md +@AGENTS.md \ No newline at end of file diff --git a/apps/dev-tool/app/mcp-server/database/_components/database-tools-interface.tsx b/apps/dev-tool/app/database/_components/database-tools-interface.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_components/database-tools-interface.tsx rename to apps/dev-tool/app/database/_components/database-tools-interface.tsx diff --git a/apps/dev-tool/app/mcp-server/database/_components/enum-browser.tsx b/apps/dev-tool/app/database/_components/enum-browser.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_components/enum-browser.tsx rename to apps/dev-tool/app/database/_components/enum-browser.tsx diff --git a/apps/dev-tool/app/mcp-server/database/_components/function-browser.tsx b/apps/dev-tool/app/database/_components/function-browser.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_components/function-browser.tsx rename to apps/dev-tool/app/database/_components/function-browser.tsx diff --git a/apps/dev-tool/app/mcp-server/database/_components/schema-explorer.tsx b/apps/dev-tool/app/database/_components/schema-explorer.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_components/schema-explorer.tsx rename to apps/dev-tool/app/database/_components/schema-explorer.tsx diff --git a/apps/dev-tool/app/mcp-server/database/_components/table-browser.tsx b/apps/dev-tool/app/database/_components/table-browser.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_components/table-browser.tsx rename to apps/dev-tool/app/database/_components/table-browser.tsx diff --git a/apps/dev-tool/app/mcp-server/database/_lib/server/database-tools.loader.ts b/apps/dev-tool/app/database/_lib/server/database-tools.loader.ts similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_lib/server/database-tools.loader.ts rename to apps/dev-tool/app/database/_lib/server/database-tools.loader.ts diff --git a/apps/dev-tool/app/mcp-server/database/_lib/server/table-server-actions.ts b/apps/dev-tool/app/database/_lib/server/table-server-actions.ts similarity index 100% rename from apps/dev-tool/app/mcp-server/database/_lib/server/table-server-actions.ts rename to apps/dev-tool/app/database/_lib/server/table-server-actions.ts diff --git a/apps/dev-tool/app/mcp-server/database/page.tsx b/apps/dev-tool/app/database/page.tsx similarity index 89% rename from apps/dev-tool/app/mcp-server/database/page.tsx rename to apps/dev-tool/app/database/page.tsx index 12f6abc93..2f1b44a59 100644 --- a/apps/dev-tool/app/mcp-server/database/page.tsx +++ b/apps/dev-tool/app/database/page.tsx @@ -4,9 +4,9 @@ import { DatabaseToolsInterface } from './_components/database-tools-interface'; import { loadDatabaseToolsData } from './_lib/server/database-tools.loader'; interface DatabasePageProps { - searchParams: { + searchParams: Promise<{ search?: string; - }; + }>; } export const metadata: Metadata = { @@ -16,7 +16,7 @@ export const metadata: Metadata = { }; async function DatabasePage({ searchParams }: DatabasePageProps) { - const searchTerm = searchParams.search || ''; + const searchTerm = (await searchParams).search || ''; // Load all database data server-side const databaseData = await loadDatabaseToolsData(); diff --git a/apps/dev-tool/app/emails/[id]/page.tsx b/apps/dev-tool/app/emails/[id]/page.tsx index aac617606..377fbe680 100644 --- a/apps/dev-tool/app/emails/[id]/page.tsx +++ b/apps/dev-tool/app/emails/[id]/page.tsx @@ -1,10 +1,12 @@ import { EmailTesterForm } from '@/app/emails/[id]/components/email-tester-form'; -import { loadEmailTemplate } from '@/app/emails/lib/email-loader'; -import { getVariable } from '@/app/variables/lib/env-scanner'; -import { EnvMode } from '@/app/variables/lib/types'; import { EnvModeSelector } from '@/components/env-mode-selector'; import { IFrame } from '@/components/iframe'; +import { + createKitEmailsDeps, + createKitEmailsService, +} from '@kit/mcp-server/emails'; +import { findWorkspaceRoot, getVariable } from '@kit/mcp-server/env'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { Button } from '@kit/ui/button'; import { @@ -15,6 +17,8 @@ import { } from '@kit/ui/dialog'; import { Page, PageBody, PageHeader } from '@kit/ui/page'; +type EnvMode = 'development' | 'production'; + type EmailPageProps = React.PropsWithChildren<{ params: Promise<{ id: string; @@ -31,25 +35,28 @@ export default async function EmailPage(props: EmailPageProps) { const { id } = await props.params; const mode = (await props.searchParams).mode ?? 'development'; - const template = await loadEmailTemplate(id); - const emailSettings = await getEmailSettings(mode); + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitEmailsService(createKitEmailsDeps(rootPath)); - const values: Record = { - emails: 'Emails', - 'invite-email': 'Invite Email', - 'account-delete-email': 'Account Delete Email', - 'confirm-email': 'Confirm Email', - 'change-email-address-email': 'Change Email Address Email', - 'reset-password-email': 'Reset Password Email', - 'magic-link-email': 'Magic Link Email', - 'otp-email': 'OTP Email', - }; + const [result, { templates }, emailSettings] = await Promise.all([ + service.read({ id }), + service.list(), + getEmailSettings(mode), + ]); + + const html = result.renderedHtml ?? result.source; + + const values: Record = { emails: 'Emails' }; + + for (const t of templates) { + values[t.id] = t.name; + } return ( } > @@ -77,7 +84,7 @@ export default async function EmailPage(props: EmailPageProps) { diff --git a/apps/dev-tool/app/emails/lib/email-loader.tsx b/apps/dev-tool/app/emails/lib/email-loader.tsx deleted file mode 100644 index 44bc9e29d..000000000 --- a/apps/dev-tool/app/emails/lib/email-loader.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - renderAccountDeleteEmail, - renderInviteEmail, - renderOtpEmail, -} from '@kit/email-templates'; - -export async function loadEmailTemplate(id: string) { - switch (id) { - case 'account-delete-email': - return renderAccountDeleteEmail({ - productName: 'Makerkit', - userDisplayName: 'Giancarlo', - }); - - case 'invite-email': - return renderInviteEmail({ - teamName: 'Makerkit', - teamLogo: '', - inviter: 'Giancarlo', - invitedUserEmail: 'test@makerkit.dev', - link: 'https://makerkit.dev', - productName: 'Makerkit', - }); - - case 'otp-email': - return renderOtpEmail({ - productName: 'Makerkit', - otp: '123456', - }); - - case 'magic-link-email': - return loadFromFileSystem('magic-link'); - - case 'reset-password-email': - return loadFromFileSystem('reset-password'); - - case 'change-email-address-email': - return loadFromFileSystem('change-email-address'); - - case 'confirm-email': - return loadFromFileSystem('confirm-email'); - - default: - throw new Error(`Email template not found: ${id}`); - } -} - -async function loadFromFileSystem(fileName: string) { - const { readFileSync } = await import('node:fs'); - const { join } = await import('node:path'); - - const filePath = join( - process.cwd(), - `../web/supabase/templates/${fileName}.html`, - ); - - return { - html: readFileSync(filePath, 'utf8'), - }; -} diff --git a/apps/dev-tool/app/emails/lib/server-actions.ts b/apps/dev-tool/app/emails/lib/server-actions.ts index 3b5a5c188..0daf69a6d 100644 --- a/apps/dev-tool/app/emails/lib/server-actions.ts +++ b/apps/dev-tool/app/emails/lib/server-actions.ts @@ -1,6 +1,10 @@ 'use server'; -import { loadEmailTemplate } from '@/app/emails/lib/email-loader'; +import { + createKitEmailsDeps, + createKitEmailsService, +} from '@kit/mcp-server/emails'; +import { findWorkspaceRoot } from '@kit/mcp-server/env'; export async function sendEmailAction(params: { template: string; @@ -27,7 +31,10 @@ export async function sendEmailAction(params: { }, }); - const { html } = await loadEmailTemplate(params.template); + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitEmailsService(createKitEmailsDeps(rootPath)); + const result = await service.read({ id: params.template }); + const html = result.renderedHtml ?? result.source; return transporter.sendMail({ html, diff --git a/apps/dev-tool/app/emails/page.tsx b/apps/dev-tool/app/emails/page.tsx index 0661160fb..5b3a0f26e 100644 --- a/apps/dev-tool/app/emails/page.tsx +++ b/apps/dev-tool/app/emails/page.tsx @@ -1,5 +1,10 @@ import Link from 'next/link'; +import { + createKitEmailsDeps, + createKitEmailsService, +} from '@kit/mcp-server/emails'; +import { findWorkspaceRoot } from '@kit/mcp-server/env'; import { CardButton, CardButtonHeader, @@ -12,7 +17,16 @@ export const metadata = { title: 'Emails', }; +const CATEGORY_LABELS: Record = { + 'supabase-auth': 'Supabase Auth Emails', + transactional: 'Transactional Emails', +}; + export default async function EmailsPage() { + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitEmailsService(createKitEmailsDeps(rootPath)); + const { templates, categories } = await service.list(); + return ( -
- Supabase Auth Emails + {categories.map((category) => { + const categoryTemplates = templates.filter( + (t) => t.category === category, + ); -
- - - - Confirm Email - - - + return ( +
+ + {CATEGORY_LABELS[category] ?? category} + - - - - Change Email Address Email - - - - - - - - Reset Password Email - - - - - - - - Magic Link Email - - - -
-
- -
- Transactional Emails - -
- - - - Account Delete Email - - - - - - - - Invite Email - - - - - - - - OTP Email - - - -
-
+
+ {categoryTemplates.map((template) => ( + + + + {template.name} + + + + ))} +
+
+ ); + })}
); diff --git a/apps/dev-tool/app/lib/connectivity-service.ts b/apps/dev-tool/app/lib/connectivity-service.ts deleted file mode 100644 index a5345d66f..000000000 --- a/apps/dev-tool/app/lib/connectivity-service.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { EnvMode } from '@/app/variables/lib/types'; - -import { getVariable } from '../variables/lib/env-scanner'; - -export function createConnectivityService(mode: EnvMode) { - return new ConnectivityService(mode); -} - -class ConnectivityService { - constructor(private mode: EnvMode = 'development') {} - - async checkSupabaseConnectivity() { - const url = await getVariable('NEXT_PUBLIC_SUPABASE_URL', this.mode); - - if (!url) { - return { - status: 'error' as const, - message: 'No Supabase URL found in environment variables', - }; - } - - const anonKey = - (await getVariable('NEXT_PUBLIC_SUPABASE_ANON_KEY', this.mode)) || - (await getVariable('NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', this.mode)); - - if (!anonKey) { - return { - status: 'error' as const, - message: 'No Supabase Anon Key found in environment variables', - }; - } - - try { - const response = await fetch(`${url}/auth/v1/health`, { - headers: { - apikey: anonKey, - Authorization: `Bearer ${anonKey}`, - }, - }); - - if (!response.ok) { - return { - status: 'error' as const, - message: - 'Failed to connect to Supabase. The Supabase Anon Key or URL is not valid.', - }; - } - - return { - status: 'success' as const, - message: 'Connected to Supabase', - }; - } catch (error) { - return { - status: 'error' as const, - message: `Failed to connect to Supabase. ${error}`, - }; - } - } - - async checkSupabaseAdminConnectivity() { - const url = await getVariable('NEXT_PUBLIC_SUPABASE_URL', this.mode); - - if (!url) { - return { - status: 'error' as const, - message: 'No Supabase URL found in environment variables', - }; - } - - const endpoint = `${url}/rest/v1/accounts`; - - const apikey = - (await getVariable('NEXT_PUBLIC_SUPABASE_ANON_KEY', this.mode)) || - (await getVariable('NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', this.mode)); - - if (!apikey) { - return { - status: 'error' as const, - message: 'No Supabase Anon Key found in environment variables', - }; - } - - const adminKey = - (await getVariable('SUPABASE_SERVICE_ROLE_KEY', this.mode)) || - (await getVariable('SUPABASE_SECRET_KEY', this.mode)); - - if (!adminKey) { - return { - status: 'error' as const, - message: 'No Supabase Service Role Key found in environment variables', - }; - } - - try { - const response = await fetch(endpoint, { - headers: { - apikey: adminKey, - }, - }); - - if (!response.ok) { - return { - status: 'error' as const, - message: - 'Failed to connect to Supabase Admin. The Supabase Service Role Key is not valid.', - }; - } - - const data = await response.json(); - - if (data.length === 0) { - return { - status: 'error' as const, - message: - 'No accounts found in Supabase Admin. The data may not be seeded. Please run `pnpm run supabase:web:reset` to reset the database.', - }; - } - - return { - status: 'success' as const, - message: 'Connected to Supabase Admin', - }; - } catch (error) { - return { - status: 'error' as const, - message: `Failed to connect to Supabase Admin. ${error}`, - }; - } - } - - async checkStripeWebhookEndpoints() { - const secretKey = await getVariable('STRIPE_SECRET_KEY', this.mode); - - if (!secretKey) { - return { - status: 'error' as const, - message: 'No Stripe Secret Key found in environment variables', - }; - } - - const webhooksSecret = await getVariable( - 'STRIPE_WEBHOOK_SECRET', - this.mode, - ); - - if (!webhooksSecret) { - return { - status: 'error' as const, - message: 'No Webhooks secret found in environment variables', - }; - } - - const url = `https://api.stripe.com`; - - const request = await fetch(`${url}/v1/webhook_endpoints`, { - headers: { - Authorization: `Bearer ${secretKey}`, - }, - }); - - if (!request.ok) { - return { - status: 'error' as const, - message: - 'Failed to connect to Stripe. The Stripe Webhook Secret is not valid.', - }; - } - - const webhooksResponse = await request.json(); - const webhooks = webhooksResponse.data ?? []; - - if (webhooks.length === 0) { - return { - status: 'error' as const, - message: 'No webhooks found in Stripe', - }; - } - - const allWebhooksShareTheSameSecret = webhooks.every( - (webhook: { secret: string }) => webhook.secret === webhooksSecret, - ); - - if (!allWebhooksShareTheSameSecret) { - return { - status: 'error' as const, - message: 'All webhooks do not share the same secret', - }; - } - - return { - status: 'success' as const, - message: 'All webhooks share the same Webhooks secret', - }; - } - - async checkStripeConnected() { - const secretKey = await getVariable('STRIPE_SECRET_KEY', this.mode); - - if (!secretKey) { - return { - status: 'error' as const, - message: 'No Stripe Secret Key found in environment variables', - }; - } - - const url = `https://api.stripe.com`; - - const request = await fetch(`${url}/v1/prices`, { - headers: { - Authorization: `Bearer ${secretKey}`, - }, - }); - - if (!request.ok) { - return { - status: 'error' as const, - message: - 'Failed to connect to Stripe. The Stripe Secret Key is not valid.', - }; - } - - return { - status: 'success' as const, - message: 'Connected to Stripe', - }; - } -} diff --git a/apps/dev-tool/app/lib/prerequisites-dashboard.loader.ts b/apps/dev-tool/app/lib/prerequisites-dashboard.loader.ts new file mode 100644 index 000000000..7992ef2eb --- /dev/null +++ b/apps/dev-tool/app/lib/prerequisites-dashboard.loader.ts @@ -0,0 +1,154 @@ +import { execFile } from 'node:child_process'; +import { access, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + type KitPrerequisitesDeps, + createKitPrerequisitesService, +} from '@kit/mcp-server/prerequisites'; + +const execFileAsync = promisify(execFile); + +export async function loadDashboardKitPrerequisites() { + const rootPath = await findWorkspaceRoot(process.cwd()); + const service = createKitPrerequisitesService( + createKitPrerequisitesDeps(rootPath), + ); + return service.check({}); +} + +function createKitPrerequisitesDeps(rootPath: string): KitPrerequisitesDeps { + return { + async getVariantFamily() { + const variant = await resolveVariant(rootPath); + return variant.includes('supabase') ? 'supabase' : 'orm'; + }, + async executeCommand(command: string, args: string[]) { + const result = await executeWithFallback(rootPath, command, args); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + }; +} + +async function executeWithFallback( + rootPath: string, + command: string, + args: string[], +) { + try { + return await execFileAsync(command, args, { + cwd: rootPath, + }); + } catch (error) { + if (isLocalCliCandidate(command)) { + const localBinCandidates = [ + join(rootPath, 'node_modules', '.bin', command), + join(rootPath, 'apps', 'web', 'node_modules', '.bin', command), + ]; + + for (const localBin of localBinCandidates) { + try { + return await execFileAsync(localBin, args, { + cwd: rootPath, + }); + } catch { + // Try next local binary candidate. + } + } + + try { + return await execFileAsync('pnpm', ['exec', command, ...args], { + cwd: rootPath, + }); + } catch { + return execFileAsync( + 'pnpm', + ['--filter', 'web', 'exec', command, ...args], + { + cwd: rootPath, + }, + ); + } + } + + throw error; + } +} + +function isLocalCliCandidate(command: string) { + return command === 'supabase' || command === 'stripe'; +} + +async function resolveVariant(rootPath: string) { + const configPath = join(rootPath, '.makerkit', 'config.json'); + + try { + await access(configPath); + const config = JSON.parse(await readFile(configPath, 'utf8')) as Record< + string, + unknown + >; + + const variant = + readString(config, 'variant') ?? + readString(config, 'template') ?? + readString(config, 'kitVariant'); + + if (variant) { + return variant; + } + } catch { + // Fall through to heuristic. + } + + if (await pathExists(join(rootPath, 'apps', 'web', 'supabase'))) { + return 'next-supabase'; + } + + return 'next-drizzle'; +} + +function readString(obj: Record, key: string) { + const value = obj[key]; + return typeof value === 'string' && value.length > 0 ? value : null; +} + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function findWorkspaceRoot(startPath: string) { + let current = startPath; + + for (let depth = 0; depth < 6; depth++) { + const workspaceManifest = join(current, 'pnpm-workspace.yaml'); + + try { + await access(workspaceManifest); + return current; + } catch { + // Continue to parent path. + } + + const parent = join(current, '..'); + + if (parent === current) { + break; + } + + current = parent; + } + + return startPath; +} diff --git a/apps/dev-tool/app/lib/status-dashboard.loader.ts b/apps/dev-tool/app/lib/status-dashboard.loader.ts new file mode 100644 index 000000000..5ed23db5f --- /dev/null +++ b/apps/dev-tool/app/lib/status-dashboard.loader.ts @@ -0,0 +1,116 @@ +import { execFile } from 'node:child_process'; +import { access, readFile, stat } from 'node:fs/promises'; +import { Socket } from 'node:net'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + type KitStatusDeps, + createKitStatusService, +} from '@kit/mcp-server/status'; + +const execFileAsync = promisify(execFile); + +export async function loadDashboardKitStatus() { + const rootPath = await findWorkspaceRoot(process.cwd()); + const service = createKitStatusService(createKitStatusDeps(rootPath)); + return service.getStatus({}); +} + +function createKitStatusDeps(rootPath: string): KitStatusDeps { + return { + rootPath, + async readJsonFile(path: string): Promise { + const filePath = join(rootPath, path); + const content = await readFile(filePath, 'utf8'); + return JSON.parse(content) as unknown; + }, + async pathExists(path: string) { + const fullPath = join(rootPath, path); + + try { + await access(fullPath); + return true; + } catch { + return false; + } + }, + async isDirectory(path: string) { + const fullPath = join(rootPath, path); + + try { + const stats = await stat(fullPath); + return stats.isDirectory(); + } catch { + return false; + } + }, + async executeCommand(command: string, args: string[]) { + const result = await execFileAsync(command, args, { + cwd: rootPath, + }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + async isPortOpen(port: number) { + return checkPort(port); + }, + getNodeVersion() { + return process.version; + }, + }; +} + +async function findWorkspaceRoot(startPath: string) { + let current = startPath; + + for (let depth = 0; depth < 6; depth++) { + const workspaceManifest = join(current, 'pnpm-workspace.yaml'); + + try { + await access(workspaceManifest); + return current; + } catch { + // Continue to parent path. + } + + const parent = join(current, '..'); + + if (parent === current) { + break; + } + + current = parent; + } + + return startPath; +} + +async function checkPort(port: number) { + return new Promise((resolve) => { + const socket = new Socket(); + + socket.setTimeout(200); + + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.once('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.once('error', () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, '127.0.0.1'); + }); +} diff --git a/apps/dev-tool/app/mcp-server/_components/mcp-server-interface.tsx b/apps/dev-tool/app/mcp-server/_components/mcp-server-interface.tsx deleted file mode 100644 index 52083983c..000000000 --- a/apps/dev-tool/app/mcp-server/_components/mcp-server-interface.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { loadPRDs } from '../_lib/server/prd-loader'; -import { McpServerTabs } from './mcp-server-tabs'; -import { PRDManagerClient } from './prd-manager-client'; - -export async function McpServerInterface() { - const initialPrds = await loadPRDs(); - - return ( - } - /> - ); -} diff --git a/apps/dev-tool/app/mcp-server/_components/mcp-server-tabs.tsx b/apps/dev-tool/app/mcp-server/_components/mcp-server-tabs.tsx deleted file mode 100644 index 2b04705b0..000000000 --- a/apps/dev-tool/app/mcp-server/_components/mcp-server-tabs.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { DatabaseIcon, FileTextIcon } from 'lucide-react'; - -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@kit/ui/tabs'; - -interface McpServerTabsProps { - prdManagerContent: React.ReactNode; - databaseToolsContent?: React.ReactNode; -} - -export function McpServerTabs({ - prdManagerContent, - databaseToolsContent, -}: McpServerTabsProps) { - return ( -
- - - - - Database Tools - - - - PRD Manager - - - - - {databaseToolsContent || ( -
-
- -

Database Tools

-

- Explore database schemas, tables, functions, and enums -

-
-
- )} -
- - - {prdManagerContent} - -
-
- ); -} diff --git a/apps/dev-tool/app/mcp-server/_components/prd-manager-client.tsx b/apps/dev-tool/app/mcp-server/_components/prd-manager-client.tsx deleted file mode 100644 index 2d30a9b05..000000000 --- a/apps/dev-tool/app/mcp-server/_components/prd-manager-client.tsx +++ /dev/null @@ -1,188 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -import Link from 'next/link'; - -import { CalendarIcon, FileTextIcon, PlusIcon, SearchIcon } from 'lucide-react'; - -import { Button } from '@kit/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; -import { - Dialog, - DialogClose, - DialogContent, - DialogHeader, - DialogTitle, -} from '@kit/ui/dialog'; -import { Input } from '@kit/ui/input'; -import { Progress } from '@kit/ui/progress'; - -import type { CreatePRDData } from '../_lib/schemas/create-prd.schema'; -import { createPRDAction } from '../_lib/server/prd-server-actions'; -import { CreatePRDForm } from './create-prd-form'; - -interface PRDSummary { - filename: string; - title: string; - lastUpdated: string; - progress: number; - totalStories: number; - completedStories: number; -} - -interface PRDManagerClientProps { - initialPrds: PRDSummary[]; -} - -export function PRDManagerClient({ initialPrds }: PRDManagerClientProps) { - const [prds, setPrds] = useState(initialPrds); - const [searchTerm, setSearchTerm] = useState(''); - const [showCreateForm, setShowCreateForm] = useState(false); - - const handleCreatePRD = async (data: CreatePRDData) => { - const result = await createPRDAction(data); - - if (result.success && result.data) { - const newPRD: PRDSummary = { - filename: result.data.filename, - title: result.data.title, - lastUpdated: result.data.lastUpdated, - progress: result.data.progress, - totalStories: result.data.totalStories, - completedStories: result.data.completedStories, - }; - - setPrds((prev) => [...prev, newPRD]); - setShowCreateForm(false); - - // Note: In a production app, you might want to trigger a router.refresh() - // to reload the server component and get the most up-to-date data - } else { - // Error handling will be managed by the form component via the action result - throw new Error(result.error || 'Failed to create PRD'); - } - }; - - const filteredPrds = prds.filter( - (prd) => - prd.title.toLowerCase().includes(searchTerm.toLowerCase()) || - prd.filename.toLowerCase().includes(searchTerm.toLowerCase()), - ); - - return ( -
- {/* Header with search and create button */} -
-
- - - setSearchTerm(e.target.value)} - className="pl-10" - /> -
- - -
- - {/* PRD List */} - {filteredPrds.length === 0 ? ( - - -
- - -

- {searchTerm ? 'No PRDs match your search' : 'No PRDs found'} -

- - {!searchTerm && ( - - )} -
-
-
- ) : ( -
- {filteredPrds.map((prd) => ( - - - - - - {prd.title} - - - - {/* Progress */} -
-
- Progress - - {prd.completedStories}/{prd.totalStories} stories - -
- -
- {prd.progress}% -
-
- - {/* Metadata */} -
- - Updated {prd.lastUpdated} -
- - {/* Filename */} -
- {prd.filename} -
-
-
- - ))} -
- )} - - {/* Create PRD Form Modal */} - {showCreateForm && ( - - - - Create New PRD - - -
- -
-
-
- )} -
- ); -} diff --git a/apps/dev-tool/app/mcp-server/page.tsx b/apps/dev-tool/app/mcp-server/page.tsx deleted file mode 100644 index c6a377494..000000000 --- a/apps/dev-tool/app/mcp-server/page.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import Link from 'next/link'; - -import { withI18n } from '@/lib/i18n/with-i18n'; -import { DatabaseIcon, FileTextIcon, ServerIcon } from 'lucide-react'; - -import { Button } from '@kit/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; -import { Page, PageBody, PageHeader } from '@kit/ui/page'; - -export const metadata = { - title: 'MCP Server', - description: - 'MCP Server development interface for database exploration and PRD management', -}; - -function McpServerPage() { - return ( - -
- - - -
-
- {/* Welcome Section */} -
- -

- Welcome to MCP Server Tools -

-

- Choose from the tools below to explore your database schema or - manage your Product Requirements Documents. Use the sidebar - navigation for quick access to specific tools. -

-
- - {/* Tool Cards */} -
- - - - - Database Tools - - - -

- Explore database schemas, tables, functions, and enums - through an intuitive interface. -

-
    -
  • • Browse database schemas and their structure
  • -
  • • Explore tables with columns and relationships
  • -
  • - • Discover database functions and their parameters -
  • -
  • • View enum types and their values
  • -
- -
-
- - - - - - PRD Manager - - - -

- Create and manage Product Requirements Documents with user - stories and progress tracking. -

-
    -
  • • Create and edit PRDs with structured templates
  • -
  • • Manage user stories with priority tracking
  • -
  • • Track progress and project status
  • -
  • • Export PRDs to markdown format
  • -
- -
-
-
-
-
-
-
-
- ); -} - -export default withI18n(McpServerPage); diff --git a/apps/dev-tool/app/page.tsx b/apps/dev-tool/app/page.tsx index eeff232c8..c87944362 100644 --- a/apps/dev-tool/app/page.tsx +++ b/apps/dev-tool/app/page.tsx @@ -1,25 +1,37 @@ -import { EnvMode } from '@/app/variables/lib/types'; -import { EnvModeSelector } from '@/components/env-mode-selector'; import { ServiceCard } from '@/components/status-tile'; +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; import { Page, PageBody, PageHeader } from '@kit/ui/page'; -import { createConnectivityService } from './lib/connectivity-service'; +import { loadDashboardKitPrerequisites } from './lib/prerequisites-dashboard.loader'; +import { loadDashboardKitStatus } from './lib/status-dashboard.loader'; -type DashboardPageProps = React.PropsWithChildren<{ - searchParams: Promise<{ mode?: EnvMode }>; -}>; +export default async function DashboardPage() { + const [status, prerequisites] = await Promise.all([ + loadDashboardKitStatus(), + loadDashboardKitPrerequisites(), + ]); -export default async function DashboardPage(props: DashboardPageProps) { - const mode = (await props.searchParams).mode ?? 'development'; - const connectivityService = createConnectivityService(mode); + const failedRequiredCount = prerequisites.prerequisites.filter( + (item) => item.required && item.status === 'fail', + ).length; - const [supabaseStatus, supabaseAdminStatus, stripeStatus] = await Promise.all( - [ - connectivityService.checkSupabaseConnectivity(), - connectivityService.checkSupabaseAdminConnectivity(), - connectivityService.checkStripeConnected(), - ], + const warnCount = prerequisites.prerequisites.filter( + (item) => item.status === 'warn', + ).length; + + const failedRequired = prerequisites.prerequisites.filter( + (item) => item.required && item.status === 'fail', + ); + + const prerequisiteRemedies = Array.from( + new Set( + failedRequired.flatMap((item) => [ + ...(item.remedies ?? []), + ...(item.install_command ? [item.install_command] : []), + ...(item.install_url ? [item.install_url] : []), + ]), + ), ); return ( @@ -27,17 +39,148 @@ export default async function DashboardPage(props: DashboardPageProps) { - - + description={'Kit MCP status for this workspace'} + />
- - - + + + + + + + + + + + + + + +
+ + {failedRequired.length > 0 ? ( + + + Prerequisites Details + + + +
+

Missing or Mismatched

+
    + {failedRequired.map((item) => ( +
  • + {item.name} + {item.version + ? ` (installed ${item.version}, requires >= ${item.minimum_version})` + : ' (not installed)'} +
  • + ))} +
+
+ +
+

Remediation

+
    + {prerequisiteRemedies.map((remedy) => ( +
  • + {remedy} +
  • + ))} +
+
+
+
+ ) : null}
); diff --git a/apps/dev-tool/app/mcp-server/prds/[filename]/_components/prd-detail-view.tsx b/apps/dev-tool/app/prds/[filename]/_components/prd-detail-view.tsx similarity index 98% rename from apps/dev-tool/app/mcp-server/prds/[filename]/_components/prd-detail-view.tsx rename to apps/dev-tool/app/prds/[filename]/_components/prd-detail-view.tsx index 93e779063..b6e998208 100644 --- a/apps/dev-tool/app/mcp-server/prds/[filename]/_components/prd-detail-view.tsx +++ b/apps/dev-tool/app/prds/[filename]/_components/prd-detail-view.tsx @@ -8,8 +8,8 @@ import { Progress } from '@kit/ui/progress'; import { Separator } from '@kit/ui/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@kit/ui/tabs'; -import { UserStoryDisplay } from '../../../_components/user-story-display'; -import type { PRDData } from '../../../_lib/server/prd-page.loader'; +import { UserStoryDisplay } from '../../_components/user-story-display'; +import type { PRDData } from '../../_lib/server/prd-page.loader'; interface PRDDetailViewProps { filename: string; diff --git a/apps/dev-tool/app/mcp-server/prds/[filename]/page.tsx b/apps/dev-tool/app/prds/[filename]/page.tsx similarity index 92% rename from apps/dev-tool/app/mcp-server/prds/[filename]/page.tsx rename to apps/dev-tool/app/prds/[filename]/page.tsx index dba45fb1e..f03946d63 100644 --- a/apps/dev-tool/app/mcp-server/prds/[filename]/page.tsx +++ b/apps/dev-tool/app/prds/[filename]/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; -import { loadPRDPageData } from '../../_lib/server/prd-page.loader'; +import { loadPRDPageData } from '../_lib/server/prd-page.loader'; import { PRDDetailView } from './_components/prd-detail-view'; interface PRDPageProps { diff --git a/apps/dev-tool/app/mcp-server/prds/_components/prds-list-interface.tsx b/apps/dev-tool/app/prds/_components/prds-list-interface.tsx similarity index 98% rename from apps/dev-tool/app/mcp-server/prds/_components/prds-list-interface.tsx rename to apps/dev-tool/app/prds/_components/prds-list-interface.tsx index b3d864308..e24e9818e 100644 --- a/apps/dev-tool/app/mcp-server/prds/_components/prds-list-interface.tsx +++ b/apps/dev-tool/app/prds/_components/prds-list-interface.tsx @@ -79,7 +79,7 @@ export function PRDsListInterface({ initialPrds }: PRDsListInterfaceProps) { {filteredPrds.map((prd) => ( diff --git a/apps/dev-tool/app/mcp-server/_components/user-story-display.tsx b/apps/dev-tool/app/prds/_components/user-story-display.tsx similarity index 100% rename from apps/dev-tool/app/mcp-server/_components/user-story-display.tsx rename to apps/dev-tool/app/prds/_components/user-story-display.tsx diff --git a/apps/dev-tool/app/mcp-server/_lib/schemas/create-prd.schema.ts b/apps/dev-tool/app/prds/_lib/schemas/create-prd.schema.ts similarity index 100% rename from apps/dev-tool/app/mcp-server/_lib/schemas/create-prd.schema.ts rename to apps/dev-tool/app/prds/_lib/schemas/create-prd.schema.ts diff --git a/apps/dev-tool/app/mcp-server/_lib/server/prd-loader.ts b/apps/dev-tool/app/prds/_lib/server/prd-loader.ts similarity index 100% rename from apps/dev-tool/app/mcp-server/_lib/server/prd-loader.ts rename to apps/dev-tool/app/prds/_lib/server/prd-loader.ts diff --git a/apps/dev-tool/app/mcp-server/_lib/server/prd-page.loader.ts b/apps/dev-tool/app/prds/_lib/server/prd-page.loader.ts similarity index 100% rename from apps/dev-tool/app/mcp-server/_lib/server/prd-page.loader.ts rename to apps/dev-tool/app/prds/_lib/server/prd-page.loader.ts diff --git a/apps/dev-tool/app/mcp-server/prds/page.tsx b/apps/dev-tool/app/prds/page.tsx similarity index 88% rename from apps/dev-tool/app/mcp-server/prds/page.tsx rename to apps/dev-tool/app/prds/page.tsx index bab3d7046..418bfbb88 100644 --- a/apps/dev-tool/app/mcp-server/prds/page.tsx +++ b/apps/dev-tool/app/prds/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; -import { loadPRDs } from '../_lib/server/prd-loader'; import { PRDsListInterface } from './_components/prds-list-interface'; +import { loadPRDs } from './_lib/server/prd-loader'; export const metadata: Metadata = { title: 'PRDs - MCP Server', diff --git a/apps/dev-tool/app/translations/components/translations-comparison.tsx b/apps/dev-tool/app/translations/components/translations-comparison.tsx index 337cb3402..4c5f12ef7 100644 --- a/apps/dev-tool/app/translations/components/translations-comparison.tsx +++ b/apps/dev-tool/app/translations/components/translations-comparison.tsx @@ -33,27 +33,7 @@ import { import { cn } from '@kit/ui/utils'; import { updateTranslationAction } from '../lib/server-actions'; -import type { TranslationData, Translations } from '../lib/translations-loader'; - -function flattenTranslations( - obj: TranslationData, - prefix = '', - result: Record = {}, -) { - for (const [key, value] of Object.entries(obj)) { - const newKey = prefix ? `${prefix}.${key}` : key; - - if (typeof value === 'string') { - result[newKey] = value; - } else { - flattenTranslations(value, newKey, result); - } - } - - return result; -} - -type FlattenedTranslations = Record>; +import type { Translations } from '../lib/translations-loader'; export function TranslationsComparison({ translations, @@ -74,35 +54,24 @@ export function TranslationsComparison({ [], ); - const locales = Object.keys(translations); - const baseLocale = locales[0]!; - const namespaces = Object.keys(translations[baseLocale] || {}); + const { base_locale, locales, namespaces } = translations; const [selectedLocales, setSelectedLocales] = useState>( new Set(locales), ); - // Flatten translations for the selected namespace - const flattenedTranslations: FlattenedTranslations = {}; - const [selectedNamespace, setSelectedNamespace] = useState( - namespaces[0] as string, + namespaces[0] ?? '', ); - for (const locale of locales) { - const namespaceData = translations[locale]?.[selectedNamespace]; - - if (namespaceData) { - flattenedTranslations[locale] = flattenTranslations(namespaceData); - } else { - flattenedTranslations[locale] = {}; - } - } - // Get all unique keys across all translations const allKeys = Array.from( new Set( - Object.values(flattenedTranslations).flatMap((data) => Object.keys(data)), + locales.flatMap((locale) => + Object.keys( + translations.translations[locale]?.[selectedNamespace] ?? {}, + ), + ), ), ).sort(); @@ -143,7 +112,7 @@ export function TranslationsComparison({ return () => subscription.unsubscribe(); }, [subject$]); - if (locales.length === 0) { + if (locales.length === 0 || !base_locale) { return
No translations found
; } @@ -228,12 +197,16 @@ export function TranslationsComparison({ {visibleLocales.map((locale) => { - const translations = flattenedTranslations[locale] ?? {}; + const translationsForLocale = + translations.translations[locale]?.[selectedNamespace] ?? + {}; const baseTranslations = - flattenedTranslations[baseLocale] ?? {}; + translations.translations[base_locale]?.[ + selectedNamespace + ] ?? {}; - const value = translations[key]; + const value = translationsForLocale[key]; const baseValue = baseTranslations[key]; const isMissing = !value; const isDifferent = value !== baseValue; diff --git a/apps/dev-tool/app/translations/lib/server-actions.ts b/apps/dev-tool/app/translations/lib/server-actions.ts index c6e3b5e83..4b4a0ac60 100644 --- a/apps/dev-tool/app/translations/lib/server-actions.ts +++ b/apps/dev-tool/app/translations/lib/server-actions.ts @@ -2,10 +2,14 @@ import { revalidatePath } from 'next/cache'; -import { readFileSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:url'; import { z } from 'zod'; +import { findWorkspaceRoot } from '@kit/mcp-server/env'; +import { + createKitTranslationsDeps, + createKitTranslationsService, +} from '@kit/mcp-server/translations'; + const Schema = z.object({ locale: z.string().min(1), namespace: z.string().min(1), @@ -20,40 +24,18 @@ const Schema = z.object({ export async function updateTranslationAction(props: z.infer) { // Validate the input const { locale, namespace, key, value } = Schema.parse(props); + const rootPath = findWorkspaceRoot(process.cwd()); - const root = resolve(process.cwd(), '..'); - const filePath = `${root}apps/web/public/locales/${locale}/${namespace}.json`; + const service = createKitTranslationsService( + createKitTranslationsDeps(rootPath), + ); try { - // Read the current translations file - const translationsFile = readFileSync(filePath, 'utf8'); - const translations = JSON.parse(translationsFile) as Record; - - // Update the nested key value - const keys = key.split('.') as string[]; - let current = translations; - - // Navigate through nested objects until the second-to-last key - for (let i = 0; i < keys.length - 1; i++) { - const currentKey = keys[i] as string; - - if (!current[currentKey]) { - current[currentKey] = {}; - } - - current = current[currentKey]; - } - - // Set the value at the final key - const finalKey = keys[keys.length - 1] as string; - current[finalKey] = value; - - // Write the updated translations back to the file - writeFileSync(filePath, JSON.stringify(translations, null, 2), 'utf8'); + const result = await service.update({ locale, namespace, key, value }); revalidatePath(`/translations`); - return { success: true }; + return result; } catch (error) { console.error('Failed to update translation:', error); throw new Error('Failed to update translation'); diff --git a/apps/dev-tool/app/translations/lib/translations-loader.ts b/apps/dev-tool/app/translations/lib/translations-loader.ts index 1d90adb5d..1f457cce6 100644 --- a/apps/dev-tool/app/translations/lib/translations-loader.ts +++ b/apps/dev-tool/app/translations/lib/translations-loader.ts @@ -1,50 +1,21 @@ -import { readFileSync, readdirSync } from 'node:fs'; -import { join } from 'node:path'; - -export type TranslationData = { - [key: string]: string | TranslationData; -}; +import { findWorkspaceRoot } from '@kit/mcp-server/env'; +import { + createKitTranslationsDeps, + createKitTranslationsService, +} from '@kit/mcp-server/translations'; export type Translations = { - [locale: string]: { - [namespace: string]: TranslationData; - }; + base_locale: string; + locales: string[]; + namespaces: string[]; + translations: Record>>; }; -export async function loadTranslations() { - const localesPath = join(process.cwd(), '../web/public/locales'); - const localesDirents = readdirSync(localesPath, { withFileTypes: true }); +export async function loadTranslations(): Promise { + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitTranslationsService( + createKitTranslationsDeps(rootPath), + ); - const locales = localesDirents - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); - - const translations: Translations = {}; - - for (const locale of locales) { - translations[locale] = {}; - - const namespaces = readdirSync(join(localesPath, locale)).filter((file) => - file.endsWith('.json'), - ); - - for (const namespace of namespaces) { - const namespaceName = namespace.replace('.json', ''); - - try { - const filePath = join(localesPath, locale, namespace); - const content = readFileSync(filePath, 'utf8'); - - translations[locale][namespaceName] = JSON.parse(content); - } catch (error) { - console.warn( - `Warning: Translation file not found for locale "${locale}" and namespace "${namespaceName}"`, - ); - - translations[locale][namespaceName] = {}; - } - } - } - - return translations; + return service.list(); } diff --git a/apps/dev-tool/app/translations/page.tsx b/apps/dev-tool/app/translations/page.tsx index e11dcb057..85e6f75cd 100644 --- a/apps/dev-tool/app/translations/page.tsx +++ b/apps/dev-tool/app/translations/page.tsx @@ -1,6 +1,5 @@ import { Metadata } from 'next'; -import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { Page, PageBody, PageHeader } from '@kit/ui/page'; import { TranslationsComparison } from './components/translations-comparison'; diff --git a/apps/dev-tool/app/variables/lib/env-scanner.ts b/apps/dev-tool/app/variables/lib/env-scanner.ts index 304ea5d0a..596edab40 100644 --- a/apps/dev-tool/app/variables/lib/env-scanner.ts +++ b/apps/dev-tool/app/variables/lib/env-scanner.ts @@ -1,471 +1,6 @@ -import 'server-only'; - -import fs from 'fs/promises'; -import path from 'path'; - -import { envVariables } from './env-variables-model'; -import { - AppEnvState, - EnvFileInfo, - EnvMode, - EnvVariableState, - ScanOptions, -} from './types'; - -// Define precedence order for each mode -const ENV_FILE_PRECEDENCE: Record = { - development: [ - '.env', - '.env.development', - '.env.local', - '.env.development.local', - ], - production: [ - '.env', - '.env.production', - '.env.local', - '.env.production.local', - ], -}; - -function getSourcePrecedence(source: string, mode: EnvMode): number { - return ENV_FILE_PRECEDENCE[mode].indexOf(source); -} - -export async function scanMonorepoEnv( - options: ScanOptions, -): Promise { - const { - rootDir = path.resolve(process.cwd(), '../..'), - apps = ['web'], - mode, - } = options; - - const envTypes = ENV_FILE_PRECEDENCE[mode]; - const appsDir = path.join(rootDir, 'apps'); - const results: EnvFileInfo[] = []; - - try { - const appDirs = await fs.readdir(appsDir); - - for (const appName of appDirs) { - if (apps.length > 0 && !apps.includes(appName)) { - continue; - } - - const appDir = path.join(appsDir, appName); - const stat = await fs.stat(appDir); - - if (!stat.isDirectory()) { - continue; - } - - const appInfo: EnvFileInfo = { - appName, - filePath: appDir, - variables: [], - }; - - for (const envType of envTypes) { - const envPath = path.join(appDir, envType); - - try { - const content = await fs.readFile(envPath, 'utf-8'); - const vars = parseEnvFile(content, envType); - - appInfo.variables.push(...vars); - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.warn(`Error reading ${envPath}:`, error); - } - } - } - - if (appInfo.variables.length > 0) { - results.push(appInfo); - } - } - } catch (error) { - console.error('Error scanning monorepo:', error); - throw error; - } - - return results; -} - -function parseEnvFile(content: string, source: string) { - const variables: Array<{ key: string; value: string; source: string }> = []; - - const lines = content.split('\n'); - - for (const line of lines) { - // Skip comments and empty lines - if (line.trim().startsWith('#') || !line.trim()) { - continue; - } - - // Match KEY=VALUE pattern, handling quotes - const match = line.match(/^([^=]+)=(.*)$/); - if (match) { - const [, key = '', rawValue] = match; - let value = rawValue ?? ''; - - // Remove quotes if present - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - - // Handle escaped quotes within the value - value = value - .replace(/\\"/g, '"') - .replace(/\\'/g, "'") - .replace(/\\\\/g, '\\'); - - variables.push({ - key: key.trim(), - value: value.trim(), - source, - }); - } - } - - return variables; -} - -export function processEnvDefinitions( - envInfo: EnvFileInfo, - mode: EnvMode, -): AppEnvState { - const variableMap: Record = {}; - - // First pass: Collect all definitions - for (const variable of envInfo.variables) { - if (!variable) { - continue; - } - - const model = envVariables.find((v) => variable.key === v.name); - - if (!variableMap[variable.key]) { - variableMap[variable.key] = { - key: variable.key, - isVisible: true, - definitions: [], - effectiveValue: variable.value, - effectiveSource: variable.source, - isOverridden: false, - category: model ? model.category : 'Custom', - validation: { - success: true, - error: { - issues: [], - }, - }, - }; - } - - const varState = variableMap[variable.key]; - - if (!varState) { - continue; - } - - varState.definitions.push({ - key: variable.key, - value: variable.value, - source: variable.source, - }); - } - - // Second pass: Determine effective values and override status - for (const key in variableMap) { - const varState = variableMap[key]; - - if (!varState) { - continue; - } - - // Sort definitions by mode-specific precedence - varState.definitions.sort( - (a, b) => - getSourcePrecedence(a.source, mode) - - getSourcePrecedence(b.source, mode), - ); - - if (varState.definitions.length > 1) { - const lastDef = varState.definitions[varState.definitions.length - 1]; - - if (!lastDef) { - continue; - } - - const highestPrecedence = getSourcePrecedence(lastDef.source, mode); - - varState.isOverridden = true; - varState.effectiveValue = lastDef.value; - varState.effectiveSource = lastDef.source; - - // Check for conflicts at highest precedence - const conflictingDefs = varState.definitions.filter( - (def) => getSourcePrecedence(def.source, mode) === highestPrecedence, - ); - - if (conflictingDefs.length > 1) { - varState.effectiveSource = `${varState.effectiveSource}`; - } - } - } - - // after computing the effective values, we can check for errors - for (const key in variableMap) { - const model = envVariables.find((v) => key === v.name); - const varState = variableMap[key]; - - if (!varState) { - continue; - } - - let validation: { - success: boolean; - error: { - issues: string[]; - }; - } = { success: true, error: { issues: [] } }; - - if (model) { - const allVariables = Object.values(variableMap).reduce( - (acc, variable) => { - return { - ...acc, - [variable.key]: variable.effectiveValue, - }; - }, - {} as Record, - ); - - // First check if it's required but missing - if (model.required && !varState.effectiveValue) { - validation = { - success: false, - error: { - issues: [ - `This variable is required but missing from your environment files`, - ], - }, - }; - } else if (model.contextualValidation) { - // Then check contextual validation - const dependenciesMet = model.contextualValidation.dependencies.some( - (dep) => { - const dependencyValue = allVariables[dep.variable] ?? ''; - - return dep.condition(dependencyValue, allVariables); - }, - ); - - if (dependenciesMet) { - // Only check for missing value or run validation if dependencies are met - if (!varState.effectiveValue) { - const dependencyErrors = model.contextualValidation.dependencies - .map((dep) => { - const dependencyValue = allVariables[dep.variable] ?? ''; - - const shouldValidate = dep.condition( - dependencyValue, - allVariables, - ); - - if (shouldValidate) { - const { success } = model.contextualValidation!.validate({ - value: varState.effectiveValue, - variables: allVariables, - mode, - }); - - if (success) { - return null; - } - - return dep.message; - } - - return null; - }) - .filter((message): message is string => message !== null); - - validation = { - success: dependencyErrors.length === 0, - error: { - issues: dependencyErrors - .map((message) => message) - .filter((message) => !!message), - }, - }; - } else { - // If we have a value and dependencies are met, run contextual validation - const result = model.contextualValidation.validate({ - value: varState.effectiveValue, - variables: allVariables, - mode, - }); - - if (!result.success) { - validation = { - success: false, - error: { - issues: result.error.issues - .map((issue) => issue.message) - .filter((message) => !!message), - }, - }; - } - } - } - } else if (model.validate && varState.effectiveValue) { - // Only run regular validation if: - // 1. There's no contextual validation - // 2. There's a value to validate - const result = model.validate({ - value: varState.effectiveValue, - variables: allVariables, - mode, - }); - - if (!result.success) { - validation = { - success: false, - error: { - issues: result.error.issues - .map((issue) => issue.message) - .filter((message) => !!message), - }, - }; - } - } - } - - varState.validation = validation; - } - - // Final pass: Validate missing variables that are marked as required - // or as having contextual validation - for (const model of envVariables) { - // If the variable exists in appState, use that - const existingVar = variableMap[model.name]; - - if (existingVar) { - // If the variable is already in the map, skip it - continue; - } - - if (model.required || model.contextualValidation) { - if (model.contextualValidation) { - const allVariables = Object.values(variableMap).reduce( - (acc, variable) => { - return { - ...acc, - [variable.key]: variable.effectiveValue, - }; - }, - {} as Record, - ); - - const errors = - model?.contextualValidation?.dependencies - .map((dep) => { - const dependencyValue = allVariables[dep.variable] ?? ''; - const shouldValidate = dep.condition( - dependencyValue, - allVariables, - ); - - if (!shouldValidate) { - return []; - } - - const effectiveValue = allVariables[dep.variable] ?? ''; - - const validation = model.contextualValidation!.validate({ - value: effectiveValue, - variables: allVariables, - mode, - }); - - if (validation) { - return [dep.message]; - } - - return []; - }) - .flat() ?? ([] as string[]); - - if (errors.length === 0) { - continue; - } else { - variableMap[model.name] = { - key: model.name, - effectiveValue: '', - effectiveSource: 'MISSING', - isVisible: true, - category: model.category, - isOverridden: false, - definitions: [], - validation: { - success: false, - error: { - issues: errors.map((error) => error), - }, - }, - }; - } - } - - // If it doesn't exist but is required or has contextual validation, create an empty state - variableMap[model.name] = { - key: model.name, - effectiveValue: '', - effectiveSource: 'MISSING', - isVisible: true, - category: model.category, - isOverridden: false, - definitions: [], - validation: { - success: false, - error: { - issues: [ - `This variable is required but missing from your environment files`, - ], - }, - }, - }; - } - } - - return { - appName: envInfo.appName, - filePath: envInfo.filePath, - mode, - variables: variableMap, - }; -} - -export async function getEnvState( - options: ScanOptions, -): Promise { - const envInfos = await scanMonorepoEnv(options); - return envInfos.map((info) => processEnvDefinitions(info, options.mode)); -} - -export async function getVariable(key: string, mode: EnvMode) { - // Get the processed environment state for all apps (you can limit to 'web' via options) - const envStates = await getEnvState({ mode, apps: ['web'] }); - - // Find the state for the "web" app. - const webState = envStates.find((state) => state.appName === 'web'); - - // Return the effectiveValue based on override status. - return webState?.variables[key]?.effectiveValue ?? ''; -} +export { + getEnvState, + getVariable, + processEnvDefinitions, + scanMonorepoEnv, +} from '@kit/mcp-server/env'; diff --git a/apps/dev-tool/app/variables/lib/env-variables-model.ts b/apps/dev-tool/app/variables/lib/env-variables-model.ts index c36f70808..4f5c0ac0d 100644 --- a/apps/dev-tool/app/variables/lib/env-variables-model.ts +++ b/apps/dev-tool/app/variables/lib/env-variables-model.ts @@ -1,1339 +1,2 @@ -import { EnvMode } from '@/app/variables/lib/types'; -import { z } from 'zod'; - -type ModelType = - | 'string' - | 'longString' - | 'number' - | 'boolean' - | 'enum' - | 'url' - | 'email'; - -type Values = Array; - -export type EnvVariableModel = { - name: string; - description: string; - hint?: string; - secret?: boolean; - type?: ModelType; - values?: Values; - category: string; - required?: boolean; - deprecated?: { - reason: string; - alternative?: string; - }; - validate?: ({ - value, - variables, - mode, - }: { - value: string; - variables: Record; - mode: EnvMode; - }) => z.SafeParseReturnType; - contextualValidation?: { - dependencies: Array<{ - variable: string; - condition: (value: string, variables: Record) => boolean; - message: string; - }>; - validate: ({ - value, - variables, - mode, - }: { - value: string; - variables: Record; - mode: EnvMode; - }) => z.SafeParseReturnType; - }; -}; - -export const envVariables: EnvVariableModel[] = [ - { - name: 'NEXT_PUBLIC_SITE_URL', - description: - 'The URL of your site, used for generating absolute URLs. Must include the protocol.', - category: 'Site Configuration', - required: true, - type: 'url', - hint: `Ex. https://example.com`, - validate: ({ value, mode }) => { - if (mode === 'development') { - return z - .string() - .url({ - message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`, - }) - .safeParse(value); - } - - return z - .string() - .url({ - message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`, - }) - .startsWith( - 'https', - `The NEXT_PUBLIC_SITE_URL variable must start with https`, - ) - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_PRODUCT_NAME', - description: - "Your product's name, used consistently across the application interface.", - category: 'Site Configuration', - hint: `Ex. "My Product"`, - required: true, - type: 'string', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_PRODUCT_NAME variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SITE_TITLE', - description: - "The site's title tag content, crucial for SEO and browser display.", - category: 'Site Configuration', - required: true, - hint: `Ex. "My Product, the best product ever"`, - type: 'string', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_SITE_TITLE variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SITE_DESCRIPTION', - type: 'longString', - description: - "Your site's meta description, important for SEO optimization.", - category: 'Site Configuration', - required: true, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_SITE_DESCRIPTION variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_DEFAULT_LOCALE', - type: 'string', - description: 'Sets the default language for your application.', - category: 'Localization', - hint: `Ex. "en"`, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_DEFAULT_LOCALE variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_AUTH_PASSWORD', - description: 'Enables or disables password-based authentication.', - category: 'Authentication', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_AUTH_MAGIC_LINK', - description: 'Enables or disables magic link authentication.', - category: 'Authentication', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', - description: 'Your Cloudflare Captcha site key for form protection.', - category: 'Security', - type: 'string', - validate: ({ value }) => { - return z.string().optional().safeParse(value); - }, - }, - { - name: 'CAPTCHA_SECRET_TOKEN', - description: - 'Your Cloudflare Captcha secret token for backend verification.', - category: 'Security', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', - condition: (value) => { - return value !== ''; - }, - message: - 'CAPTCHA_SECRET_TOKEN is required when NEXT_PUBLIC_CAPTCHA_SITE_KEY is set', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_USER_NAVIGATION_STYLE', - description: - 'Controls user navigation layout. Options: sidebar, header, or custom.', - category: 'Navigation', - type: 'enum', - values: ['sidebar', 'header', 'custom'], - validate: ({ value }) => { - return z - .enum(['sidebar', 'header', 'custom']) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED', - description: 'Sets the default state of the home sidebar.', - category: 'Navigation', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_TEAM_NAVIGATION_STYLE', - description: - 'Controls team navigation layout. Options: sidebar, header, or custom.', - category: 'Navigation', - type: 'enum', - values: ['sidebar', 'header', 'custom'], - validate: ({ value }) => { - return z - .enum(['sidebar', 'header', 'custom']) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED', - description: 'Sets the default state of the team sidebar.', - category: 'Navigation', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE', - description: - 'Defines sidebar collapse behavior. Options: offcanvas, icon, or none.', - category: 'Navigation', - type: 'enum', - values: ['offcanvas', 'icon', 'none'], - validate: ({ value }) => { - return z.enum(['offcanvas', 'icon', 'none']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_DEFAULT_THEME_MODE', - description: - 'Controls the default theme appearance. Options: light, dark, or system.', - category: 'Theme', - type: 'enum', - values: ['light', 'dark', 'system'], - validate: ({ value }) => { - return z.enum(['light', 'dark', 'system']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_THEME_TOGGLE', - description: 'Controls visibility of the theme toggle feature.', - category: 'Theme', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER', - description: 'Controls visibility of the sidebar trigger feature.', - category: 'Navigation', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION', - description: 'Allows users to delete their personal accounts.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING', - description: 'Enables billing features for personal accounts.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS', - description: 'Master switch for team account functionality.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION', - description: 'Controls ability to create new team accounts.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION', - description: 'Allows team account deletion.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING', - description: 'Enables billing features for team accounts.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_NOTIFICATIONS', - description: 'Controls the notification system.', - category: 'Notifications', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_REALTIME_NOTIFICATIONS', - description: 'Enables real-time notifications using Supabase Realtime.', - category: 'Notifications', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SUPABASE_URL', - description: 'Your Supabase project URL.', - category: 'Supabase', - hint: `Ex. https://your-project.supabase.co`, - required: true, - type: 'url', - validate: ({ value, mode }) => { - if (mode === 'development') { - return z - .string() - .url({ - message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`, - }) - .safeParse(value); - } - - return z - .string() - .url({ - message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`, - }) - .startsWith( - 'https', - `The NEXT_PUBLIC_SUPABASE_URL variable must start with https`, - ) - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', - description: 'Your Supabase anonymous API key.', - category: 'Supabase', - type: 'string', - deprecated: { - reason: 'Replaced by new JWT signing key system', - alternative: 'NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_SUPABASE_ANON_KEY variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', - description: 'Your Supabase public API key.', - category: 'Supabase', - required: false, - type: 'string', - hint: 'Falls back to NEXT_PUBLIC_SUPABASE_ANON_KEY if not provided', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_SUPABASE_PUBLIC_KEY variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'SUPABASE_SERVICE_ROLE_KEY', - description: 'Your Supabase service role key (keep this secret!).', - category: 'Supabase', - secret: true, - type: 'string', - deprecated: { - reason: 'Renamed for consistency with new JWT signing key system', - alternative: 'SUPABASE_SECRET_KEY', - }, - validate: ({ value, variables }) => { - return z - .string() - .min( - 1, - `The SUPABASE_SERVICE_ROLE_KEY variable must be at least 1 character`, - ) - .refine( - (value) => { - return value !== variables['NEXT_PUBLIC_SUPABASE_ANON_KEY']; - }, - { - message: `The SUPABASE_SERVICE_ROLE_KEY variable must be different from NEXT_PUBLIC_SUPABASE_ANON_KEY`, - }, - ) - .safeParse(value); - }, - }, - { - name: 'SUPABASE_SECRET_KEY', - description: - 'Your Supabase secret key (preferred over SUPABASE_SERVICE_ROLE_KEY).', - category: 'Supabase', - secret: true, - required: false, - type: 'string', - hint: 'Falls back to SUPABASE_SERVICE_ROLE_KEY if not provided', - validate: ({ value, variables }) => { - return z - .string() - .min(1, `The SUPABASE_SECRET_KEY variable must be at least 1 character`) - .refine( - (value) => { - const anonKey = - variables['NEXT_PUBLIC_SUPABASE_ANON_KEY'] || - variables['NEXT_PUBLIC_SUPABASE_PUBLIC_KEY']; - return value !== anonKey; - }, - { - message: `The SUPABASE_SECRET_KEY variable must be different from public keys`, - }, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'SUPABASE_DB_WEBHOOK_SECRET', - description: 'Secret key for Supabase webhook verification.', - category: 'Supabase', - secret: true, - required: true, - type: 'string', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The SUPABASE_DB_WEBHOOK_SECRET variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_BILLING_PROVIDER', - description: - 'Your chosen billing provider. Options: stripe or lemon-squeezy.', - category: 'Billing', - required: true, - type: 'enum', - values: ['stripe', 'lemon-squeezy'], - validate: ({ value }) => { - return z.enum(['stripe', 'lemon-squeezy']).optional().safeParse(value); - }, - }, - { - name: 'BILLING_MODE', - description: 'Billing mode configuration for the application.', - category: 'Billing', - required: false, - type: 'enum', - values: ['subscription', 'one-time'], - deprecated: { - reason: - 'This configuration is no longer required and billing mode is now automatically determined', - alternative: undefined, - }, - validate: ({ value }) => { - return z.enum(['subscription', 'one-time']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', - description: 'Your Stripe publishable key.', - hint: `Ex. pk_test_123456789012345678901234`, - category: 'Billing', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'stripe', - message: - 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`, - ) - .refine( - (value) => { - return value.startsWith('pk_'); - }, - { - message: `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must start with pk_`, - }, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'STRIPE_SECRET_KEY', - description: 'Your Stripe secret key.', - category: 'Billing', - hint: `Ex. sk_test_123456789012345678901234`, - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'stripe', - message: - 'STRIPE_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`) - .refine( - (value) => { - return value.startsWith('sk_') || value.startsWith('rk_'); - }, - { - message: `The STRIPE_SECRET_KEY variable must start with sk_ or rk_`, - }, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`) - .optional() - .safeParse(value); - }, - }, - { - name: 'STRIPE_WEBHOOK_SECRET', - description: 'Your Stripe webhook secret.', - category: 'Billing', - hint: `Ex. whsec_123456789012345678901234`, - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'stripe', - message: - 'STRIPE_WEBHOOK_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`, - ) - .refine( - (value) => { - return value.startsWith('whsec_'); - }, - { - message: `The STRIPE_WEBHOOK_SECRET variable must start with whsec_`, - }, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'LEMON_SQUEEZY_SECRET_KEY', - description: 'Your Lemon Squeezy secret key.', - category: 'Billing', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'lemon-squeezy', - message: - 'LEMON_SQUEEZY_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'LEMON_SQUEEZY_STORE_ID', - description: 'Your Lemon Squeezy store ID.', - category: 'Billing', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'lemon-squeezy', - message: - 'LEMON_SQUEEZY_STORE_ID is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'LEMON_SQUEEZY_SIGNING_SECRET', - description: 'Your Lemon Squeezy signing secret.', - category: 'Billing', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_BILLING_PROVIDER', - condition: (value) => value === 'lemon-squeezy', - message: - 'LEMON_SQUEEZY_SIGNING_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'MAILER_PROVIDER', - description: 'Your email service provider. Options: nodemailer or resend.', - category: 'Email', - required: true, - type: 'enum', - values: ['nodemailer', 'resend'], - validate: ({ value }) => { - return z.enum(['nodemailer', 'resend']).safeParse(value); - }, - }, - { - name: 'EMAIL_SENDER', - description: 'Default sender email address.', - category: 'Email', - hint: `Ex. "Makerkit "`, - required: true, - type: 'string', - validate: ({ value }) => { - return z - .string() - .min(1, `The EMAIL_SENDER variable must be at least 1 character`) - .safeParse(value); - }, - }, - { - name: 'CONTACT_EMAIL', - description: 'Email address for contact form submissions.', - category: 'Email', - hint: `Ex. "Makerkit "`, - required: true, - type: 'email', - validate: ({ value }) => { - return z - .string() - .email() - .min(1, `The CONTACT_EMAIL variable must be at least 1 character`) - .safeParse(value); - }, - }, - { - name: 'RESEND_API_KEY', - description: 'Your Resend API key.', - category: 'Email', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'resend', - message: - 'RESEND_API_KEY is required when MAILER_PROVIDER is set to "resend"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'resend') { - return z - .string() - .min(1, `The RESEND_API_KEY variable must be at least 1 character`) - .safeParse(value); - } - - return z.string().optional().safeParse(value); - }, - }, - }, - { - name: 'EMAIL_HOST', - description: 'SMTP host for Nodemailer configuration.', - category: 'Email', - type: 'string', - hint: `Ex. "smtp.example.com"`, - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'nodemailer', - message: - 'EMAIL_HOST is required when MAILER_PROVIDER is set to "nodemailer"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'nodemailer') { - return z - .string() - .min(1, 'The EMAIL_HOST variable must be at least 1 character') - .safeParse(value); - } - return z.string().optional().safeParse(value); - }, - }, - }, - { - name: 'EMAIL_PORT', - description: 'SMTP port for Nodemailer configuration.', - category: 'Email', - type: 'number', - hint: `Ex. 587 or 465`, - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'nodemailer', - message: - 'EMAIL_PORT is required when MAILER_PROVIDER is set to "nodemailer"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'nodemailer') { - return z.coerce - .number() - .min(1, 'The EMAIL_PORT variable must be at least 1') - .max(65535, 'The EMAIL_PORT variable must be at most 65535') - .safeParse(value); - } - return z.coerce.number().optional().safeParse(value); - }, - }, - }, - { - name: 'EMAIL_USER', - description: 'SMTP user for Nodemailer configuration.', - category: 'Email', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'nodemailer', - message: - 'EMAIL_USER is required when MAILER_PROVIDER is set to "nodemailer"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'nodemailer') { - return z - .string() - .min(1, 'The EMAIL_USER variable must be at least 1 character') - .safeParse(value); - } - - return z.string().optional().safeParse(value); - }, - }, - }, - { - name: 'EMAIL_PASSWORD', - description: 'SMTP password for Nodemailer configuration.', - category: 'Email', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'nodemailer', - message: - 'EMAIL_PASSWORD is required when MAILER_PROVIDER is set to "nodemailer"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'nodemailer') { - return z - .string() - .min(1, 'The EMAIL_PASSWORD variable must be at least 1 character') - .safeParse(value); - } - return z.string().optional().safeParse(value); - }, - }, - }, - { - name: 'EMAIL_TLS', - description: - 'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.', - category: 'Email', - type: 'boolean', - contextualValidation: { - dependencies: [ - { - variable: 'MAILER_PROVIDER', - condition: (value) => value === 'nodemailer', - message: - 'EMAIL_TLS is required when MAILER_PROVIDER is set to "nodemailer"', - }, - ], - validate: ({ value, variables }) => { - if (variables['MAILER_PROVIDER'] === 'nodemailer') { - return z.coerce.boolean().optional().safeParse(value); - } - return z.coerce.boolean().optional().safeParse(value); - }, - }, - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'CMS_CLIENT', - description: 'Your chosen CMS system. Options: wordpress or keystatic.', - category: 'CMS', - type: 'enum', - values: ['wordpress', 'keystatic'], - validate: ({ value }) => { - return z.enum(['wordpress', 'keystatic']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND', - description: 'Your Keystatic storage kind. Options: local, cloud, github.', - category: 'CMS', - type: 'enum', - values: ['local', 'cloud', 'github'], - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value) => value === 'keystatic', - message: - 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND is required when CMS_CLIENT is set to "keystatic"', - }, - ], - validate: ({ value, variables }) => { - if (variables['CMS_CLIENT'] === 'keystatic') { - return z - .enum(['local', 'cloud', 'github']) - .optional() - .safeParse(value); - } - return z.enum(['local', 'cloud', 'github']).optional().safeParse(value); - }, - }, - }, - { - name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO', - description: 'Your Keystatic storage repo.', - category: 'CMS', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value, variables) => - value === 'keystatic' && - variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github', - message: - 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO is required when CMS_CLIENT is set to "keystatic"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - }, - { - name: 'KEYSTATIC_GITHUB_TOKEN', - description: 'Your Keystatic GitHub token.', - category: 'CMS', - secret: true, - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value, variables) => - value === 'keystatic' && - variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github', - message: - 'KEYSTATIC_GITHUB_TOKEN is required when CMS_CLIENT is set to "keystatic"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The KEYSTATIC_GITHUB_TOKEN variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - }, - { - name: 'KEYSTATIC_PATH_PREFIX', - description: 'Your Keystatic path prefix.', - category: 'CMS', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value) => value === 'keystatic', - message: - 'KEYSTATIC_PATH_PREFIX is required when CMS_CLIENT is set to "keystatic"', - }, - ], - validate: ({ value }) => { - return z.string().safeParse(value); - }, - }, - }, - { - name: 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH', - description: 'Your Keystatic content path.', - category: 'CMS', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value) => value === 'keystatic', - message: - 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH is required when CMS_CLIENT is set to "keystatic"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - }, - { - name: 'WORDPRESS_API_URL', - description: 'WordPress API URL when using WordPress as CMS.', - category: 'CMS', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'CMS_CLIENT', - condition: (value) => value === 'wordpress', - message: - 'WORDPRESS_API_URL is required when CMS_CLIENT is set to "wordpress"', - }, - ], - validate: ({ value }) => { - return z - .string() - .url({ - message: `The WORDPRESS_API_URL variable must be a valid URL`, - }) - .safeParse(value); - }, - }, - }, - { - name: 'NEXT_PUBLIC_LOCALES_PATH', - description: 'The path to your locales folder.', - category: 'Localization', - type: 'string', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_LOCALES_PATH variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_LANGUAGE_PRIORITY', - description: 'The priority setting as to how infer the language.', - category: 'Localization', - type: 'enum', - values: ['user', 'application'], - validate: ({ value }) => { - return z.enum(['user', 'application']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_ENABLE_VERSION_UPDATER', - description: - 'Enables the version updater to poll the latest version and notify the user.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS', - description: 'The interval in seconds to check for updates.', - category: 'Features', - type: 'number', - validate: ({ value }) => { - return z.coerce - .number() - .min( - 1, - `The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at least 1 character`, - ) - .max( - 86400, - `The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at most 86400`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: `ENABLE_REACT_COMPILER`, - description: 'Enables the React compiler [experimental]', - category: 'Build', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_MONITORING_PROVIDER', - description: 'The monitoring provider to use.', - category: 'Monitoring', - type: 'enum', - values: ['baselime', 'sentry', 'none'], - validate: ({ value }) => { - return z.enum(['baselime', 'sentry', '']).optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_SENTRY_DSN', - description: 'The Sentry DSN to use.', - category: 'Monitoring', - type: `string`, - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_MONITORING_PROVIDER', - condition: (value) => value === 'sentry', - message: - 'NEXT_PUBLIC_SENTRY_DSN is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "sentry"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_SENTRY_DSN variable must be at least 1 character`, - ) - .safeParse(value); - }, - }, - }, - { - name: 'NEXT_PUBLIC_SENTRY_ENVIRONMENT', - description: 'The Sentry environment to use.', - category: 'Monitoring', - type: 'string', - validate: ({ value }) => { - return z.string().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_BASELIME_KEY', - description: 'The Baselime key to use.', - category: 'Monitoring', - type: 'string', - contextualValidation: { - dependencies: [ - { - variable: 'NEXT_PUBLIC_MONITORING_PROVIDER', - condition: (value) => value === 'baselime', - message: - 'NEXT_PUBLIC_BASELIME_KEY is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "baselime"', - }, - ], - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_BASELIME_KEY variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - }, - { - name: 'STRIPE_ENABLE_TRIAL_WITHOUT_CC', - description: 'Enables trial plans without credit card.', - category: 'Billing', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_THEME_COLOR', - description: 'The default theme color.', - category: 'Theme', - type: 'string', - required: true, - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_THEME_COLOR variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_THEME_COLOR_DARK', - description: 'The default theme color for dark mode.', - category: 'Theme', - required: true, - type: 'string', - validate: ({ value }) => { - return z - .string() - .min( - 1, - `The NEXT_PUBLIC_THEME_COLOR_DARK variable must be at least 1 character`, - ) - .optional() - .safeParse(value); - }, - }, - { - name: 'NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX', - description: 'Whether to display the terms checkbox during sign-up.', - category: 'Features', - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, - { - name: 'ENABLE_STRICT_CSP', - description: 'Enables strict Content Security Policy (CSP) headers.', - category: 'Security', - required: false, - type: 'boolean', - validate: ({ value }) => { - return z.coerce.boolean().optional().safeParse(value); - }, - }, -]; +export { envVariables } from '@kit/mcp-server/env/model'; +export type { EnvVariableModel } from '@kit/mcp-server/env/model'; diff --git a/apps/dev-tool/app/variables/lib/server-actions.ts b/apps/dev-tool/app/variables/lib/server-actions.ts index b64d00c6b..bfef031a7 100644 --- a/apps/dev-tool/app/variables/lib/server-actions.ts +++ b/apps/dev-tool/app/variables/lib/server-actions.ts @@ -2,83 +2,35 @@ import { revalidatePath } from 'next/cache'; -import { envVariables } from '@/app/variables/lib/env-variables-model'; -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:url'; import { z } from 'zod'; +import { + createKitEnvDeps, + createKitEnvService, + findWorkspaceRoot, +} from '@kit/mcp-server/env'; + const Schema = z.object({ name: z.string().min(1), value: z.string(), mode: z.enum(['development', 'production']), }); -/** - * Update the environment variable in the specified file. - * @param props - */ export async function updateEnvironmentVariableAction( props: z.infer, ) { - // Validate the input const { name, mode, value } = Schema.parse(props); - const root = resolve(process.cwd(), '..'); - const model = envVariables.find((item) => item.name === name); - // Determine the source file based on the mode - const source = (() => { - const isSecret = model?.secret ?? true; + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitEnvService(createKitEnvDeps(rootPath)); - switch (mode) { - case 'development': - if (isSecret) { - return '.env.local'; - } else { - return '.env.development'; - } + const result = await service.update({ + key: name, + value, + mode, + }); - case 'production': - if (isSecret) { - return '.env.production.local'; - } else { - return '.env.production'; - } + revalidatePath('/variables'); - default: - throw new Error(`Invalid mode: ${mode}`); - } - })(); - - // check file exists, if not, create it - const filePath = `${root}/apps/web/${source}`; - - if (!existsSync(filePath)) { - writeFileSync(filePath, '', 'utf8'); - } - - const sourceEnvFile = readFileSync(`${root}apps/web/${source}`, 'utf8'); - - let updatedEnvFile = ''; - const isInSourceFile = sourceEnvFile.includes(name); - const isCommentedOut = sourceEnvFile.includes(`#${name}=`); - - if (isInSourceFile && !isCommentedOut) { - updatedEnvFile = sourceEnvFile.replace( - new RegExp(`^${name}=.*`, 'm'), - `${name}=${value}`, - ); - } else { - // if the key does not exist, append it to the end of the file - updatedEnvFile = `${sourceEnvFile}\n${name}=${value}`; - } - - // write the updated content back to the file - writeFileSync(`${root}/apps/web/${source}`, updatedEnvFile, 'utf8'); - - revalidatePath(`/variables`); - - return { - success: true, - message: `Updated ${name} in "${source}"`, - }; + return result; } diff --git a/apps/dev-tool/app/variables/page.tsx b/apps/dev-tool/app/variables/page.tsx index 81acd4568..ca312dd43 100644 --- a/apps/dev-tool/app/variables/page.tsx +++ b/apps/dev-tool/app/variables/page.tsx @@ -1,12 +1,12 @@ import { use } from 'react'; -import { - processEnvDefinitions, - scanMonorepoEnv, -} from '@/app/variables/lib/env-scanner'; import { EnvMode } from '@/app/variables/lib/types'; -import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; +import { + createKitEnvDeps, + createKitEnvService, + findWorkspaceRoot, +} from '@kit/mcp-server/env'; import { Page, PageBody, PageHeader } from '@kit/ui/page'; import { AppEnvironmentVariablesManager } from './components/app-environment-variables-manager'; @@ -21,7 +21,7 @@ export const metadata = { export default function VariablesPage({ searchParams }: VariablesPageProps) { const { mode = 'development' } = use(searchParams); - const apps = use(scanMonorepoEnv({ mode })); + const apps = use(loadEnvStates(mode)); return ( @@ -36,19 +36,18 @@ export default function VariablesPage({ searchParams }: VariablesPageProps) {
- {apps.map((app) => { - const appEnvState = processEnvDefinitions(app, mode); - - return ( - - ); - })} + {apps.map((app) => ( + + ))}
); } + +async function loadEnvStates(mode: EnvMode) { + const rootPath = findWorkspaceRoot(process.cwd()); + const service = createKitEnvService(createKitEnvDeps(rootPath)); + return service.getAppState(mode); +} diff --git a/apps/dev-tool/components/app-sidebar.tsx b/apps/dev-tool/components/app-sidebar.tsx index e030dd241..f47652cb6 100644 --- a/apps/dev-tool/components/app-sidebar.tsx +++ b/apps/dev-tool/components/app-sidebar.tsx @@ -11,7 +11,6 @@ import { LanguagesIcon, LayoutDashboardIcon, MailIcon, - ServerIcon, } from 'lucide-react'; import { @@ -55,20 +54,14 @@ const routes = [ Icon: LanguagesIcon, }, { - label: 'MCP Server', - Icon: ServerIcon, - children: [ - { - label: 'Database', - path: '/mcp-server/database', - Icon: DatabaseIcon, - }, - { - label: 'PRD Manager', - path: '/mcp-server/prds', - Icon: FileTextIcon, - }, - ], + label: 'Database', + path: '/database', + Icon: DatabaseIcon, + }, + { + label: 'PRD Manager', + path: '/prds', + Icon: FileTextIcon, }, ]; diff --git a/apps/dev-tool/components/status-tile.tsx b/apps/dev-tool/components/status-tile.tsx index 2f8eb976a..3d3048d7c 100644 --- a/apps/dev-tool/components/status-tile.tsx +++ b/apps/dev-tool/components/status-tile.tsx @@ -2,12 +2,13 @@ import { AlertCircle, CheckCircle2, XCircle } from 'lucide-react'; -import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Card, CardContent } from '@kit/ui/card'; export const ServiceStatus = { CHECKING: 'checking', SUCCESS: 'success', + WARNING: 'warning', + INFO: 'info', ERROR: 'error', } as const; @@ -16,6 +17,8 @@ type ServiceStatusType = (typeof ServiceStatus)[keyof typeof ServiceStatus]; const StatusIcons = { [ServiceStatus.CHECKING]: , [ServiceStatus.SUCCESS]: , + [ServiceStatus.WARNING]: , + [ServiceStatus.INFO]: , [ServiceStatus.ERROR]: , }; @@ -30,7 +33,7 @@ interface ServiceCardProps { export const ServiceCard = ({ name, status }: ServiceCardProps) => { return ( - +
diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 202cf5875..679c8b759 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -11,7 +11,7 @@ }, "author": "Makerkit", "devDependencies": { - "@playwright/test": "^1.58.1", + "@playwright/test": "^1.58.2", "@supabase/supabase-js": "catalog:", "@types/node": "catalog:", "dotenv": "17.2.4", diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index c33fefdd8..220dd5b87 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -22,6 +22,7 @@ app/ For specialized implementation: - `/feature-builder` - End-to-end feature implementation +- `/service-builder` - Server side services - `/server-action-builder` - Server actions - `/forms-builder` - Forms with validation - `/navigation-config` - Adding routes and menu items diff --git a/apps/web/app/(marketing)/changelog/[slug]/page.tsx b/apps/web/app/(marketing)/changelog/[slug]/page.tsx index 5aae60cfa..5a700a790 100644 --- a/apps/web/app/(marketing)/changelog/[slug]/page.tsx +++ b/apps/web/app/(marketing)/changelog/[slug]/page.tsx @@ -72,10 +72,10 @@ export async function generateMetadata({ url: data.entry.url, images: image ? [ - { - url: image, - }, - ] + { + url: image, + }, + ] : [], }, twitter: { diff --git a/apps/web/app/admin/AGENTS.md b/apps/web/app/admin/AGENTS.md index 59006243c..80ad1dc70 100644 --- a/apps/web/app/admin/AGENTS.md +++ b/apps/web/app/admin/AGENTS.md @@ -6,6 +6,7 @@ - **ALWAYS** validate admin status before operations - **NEVER** bypass authentication or authorization - **ALWAYS** audit admin operations with logging +- **ALWAYS** use `adminAction` to wrap admin actions @packages/features/admin/src/lib/server/utils/admin-action.ts ## Page Structure diff --git a/apps/web/components/error-page-content.tsx b/apps/web/components/error-page-content.tsx index 960b8190c..f3efe1260 100644 --- a/apps/web/components/error-page-content.tsx +++ b/apps/web/components/error-page-content.tsx @@ -69,7 +69,7 @@ export function ErrorPageContent({ ) : ( @@ -77,7 +77,7 @@ export function ErrorPageContent({ diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 02a443794..6ee5f40ba 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -34,7 +34,7 @@ const config = { fullUrl: true, }, }, - serverExternalPackages: ['pino', 'thread-stream'], + serverExternalPackages: [], // needed for supporting dynamic imports for local content outputFileTracingIncludes: { '/*': ['./content/**/*'], diff --git a/package.json b/package.json index 7bfa9c71b..15466a39a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-supabase-saas-kit-turbo", - "version": "2.23.14", + "version": "2.24.0", "private": true, "sideEffects": false, "engines": { @@ -48,7 +48,7 @@ "@turbo/gen": "^2.7.6", "cross-env": "^10.0.0", "prettier": "^3.8.1", - "turbo": "2.7.6", + "turbo": "2.8.5", "typescript": "^5.9.3" } } diff --git a/packages/email-templates/AGENTS.md b/packages/email-templates/AGENTS.md new file mode 100644 index 000000000..357ae3641 --- /dev/null +++ b/packages/email-templates/AGENTS.md @@ -0,0 +1,22 @@ +# Email Templates Instructions + +This package owns transactional email templates and renderers using React Email. + +## Non-negotiables + +1. New email must be added to `src/registry.ts` (`EMAIL_TEMPLATE_RENDERERS`) or dynamic inclusion/discovery will miss it. +2. New email renderer must be exported from `src/index.ts`. +3. Renderer contract: async function returning `{ html, subject }`. +4. i18n namespace must match locale filename in `src/locales//.json`. +5. Reuse shared primitives in `src/components/*` for layout/style consistency. +6. Include one clear CTA and a plain URL fallback in body copy. +7. Keep subject/body concise, action-first, non-spammy. + +## When adding a new email + +1. Add template in `src/emails/*.email.tsx`. +2. Add locale file in `src/locales/en/*-email.json` if template uses i18n. +3. Export template renderer from `src/index.ts`. +4. Add renderer to `src/registry.ts` (`EMAIL_TEMPLATE_RENDERERS`). + +`src/registry.ts` is required for dynamic inclusion/discovery. If not added there, dynamic template listing/rendering will miss it. diff --git a/packages/email-templates/CLAUDE.md b/packages/email-templates/CLAUDE.md new file mode 100644 index 000000000..eef4bd20c --- /dev/null +++ b/packages/email-templates/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md \ No newline at end of file diff --git a/packages/email-templates/package.json b/packages/email-templates/package.json index 8c1174856..2bc2b00f1 100644 --- a/packages/email-templates/package.json +++ b/packages/email-templates/package.json @@ -10,7 +10,8 @@ }, "prettier": "@kit/prettier-config", "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./registry": "./src/registry.ts" }, "dependencies": { "@react-email/components": "catalog:" @@ -20,7 +21,10 @@ "@kit/i18n": "workspace:*", "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", - "@types/node": "catalog:" + "@types/node": "catalog:", + "@types/react": "catalog:", + "react": "catalog:", + "react-dom": "catalog:" }, "typesVersions": { "*": { diff --git a/packages/email-templates/src/registry.ts b/packages/email-templates/src/registry.ts new file mode 100644 index 000000000..e773bae6a --- /dev/null +++ b/packages/email-templates/src/registry.ts @@ -0,0 +1,39 @@ +import { renderAccountDeleteEmail } from './emails/account-delete.email'; +import { renderInviteEmail } from './emails/invite.email'; +import { renderOtpEmail } from './emails/otp.email'; + +/** + * Registry of email template renderers. + * + * This is used to render email templates dynamically. Ex. list all available email templates in the MCP server. + * + * @example + * + * const { html, subject } = await renderAccountDeleteEmail({ + * userDisplayName: 'John Doe', + * productName: 'My SaaS App', + * }); + * + * await mailer.sendEmail({ + * to: 'user@example.com', + * from: 'noreply@yourdomain.com', + * subject, + * html, + * }); + * + * @example + * + * const { html, subject } = await renderAccountDeleteEmail({ + * userDisplayName: 'John Doe', + * productName: 'My SaaS App', + * }); + * + */ +export const EMAIL_TEMPLATE_RENDERERS = { + 'account-delete-email': renderAccountDeleteEmail, + 'invite-email': renderInviteEmail, + 'otp-email': renderOtpEmail, +}; + +export type EmailTemplateRenderer = + (typeof EMAIL_TEMPLATE_RENDERERS)[keyof typeof EMAIL_TEMPLATE_RENDERERS]; diff --git a/packages/i18n/src/create-i18n-settings.ts b/packages/i18n/src/create-i18n-settings.ts index f388aaceb..548703084 100644 --- a/packages/i18n/src/create-i18n-settings.ts +++ b/packages/i18n/src/create-i18n-settings.ts @@ -22,6 +22,7 @@ export function createI18nSettings({ supportedLngs: languages, fallbackLng: languages[0], detection: undefined, + showSupportNotice: false, lng, preload: false as const, lowerCaseLng: true as const, diff --git a/packages/i18n/src/i18n.client.ts b/packages/i18n/src/i18n.client.ts index d9a92536b..64be377cf 100644 --- a/packages/i18n/src/i18n.client.ts +++ b/packages/i18n/src/i18n.client.ts @@ -42,6 +42,7 @@ export async function initializeI18nClient( .init( { ...settings, + showSupportNotice: false, detection: { order: ['cookie', 'htmlTag', 'navigator'], caches: ['cookie'], diff --git a/packages/mcp-server/AGENTS.md b/packages/mcp-server/AGENTS.md new file mode 100644 index 000000000..08e57327e --- /dev/null +++ b/packages/mcp-server/AGENTS.md @@ -0,0 +1,16 @@ +# MCP Server Instructions + +This package owns Makerkit MCP tool/resource registration and adapters. + +## Non-negotiables + +1. Use service pattern: keep business/domain logic in `*.service.ts`, not MCP handlers. +2. `index.ts` in each tool folder is adapter only: parse input, call service, map output/errors. +3. Inject deps via `create*Deps` + `create*Service`; avoid hidden globals/singletons. +4. Keep schemas in `schema.ts`; validate all tool input with zod before service call. +5. Export public registration + service factory/types from each tool `index.ts`. +6. Add/maintain unit tests for service behavior and tool adapter behavior. +7. Register new tools/resources in `src/index.ts`. +8. Keep tool responses structured + stable; avoid breaking output shapes. + +Service pattern is required to decouple logic from MCP server transport and keep logic testable/reusable. diff --git a/packages/mcp-server/CLAUDE.md b/packages/mcp-server/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/packages/mcp-server/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 8abba17a8..1a8371f01 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -2,10 +2,10 @@ "name": "@kit/mcp-server", "private": true, "version": "0.1.0", - "main": "./build/index.js", - "module": true, + "type": "module", + "main": "./build/index.cjs", "bin": { - "makerkit-mcp-server": "./build/index.js" + "makerkit-mcp-server": "./build/index.cjs" }, "exports": { "./database": "./src/tools/database.ts", @@ -13,28 +13,37 @@ "./migrations": "./src/tools/migrations.ts", "./prd-manager": "./src/tools/prd-manager.ts", "./prompts": "./src/tools/prompts.ts", - "./scripts": "./src/tools/scripts.ts" + "./scripts": "./src/tools/scripts.ts", + "./status": "./src/tools/status/index.ts", + "./prerequisites": "./src/tools/prerequisites/index.ts", + "./env": "./src/tools/env/index.ts", + "./env/model": "./src/tools/env/model.ts", + "./env/types": "./src/tools/env/types.ts", + "./dev": "./src/tools/dev/index.ts", + "./db": "./src/tools/db/index.ts", + "./emails": "./src/tools/emails/index.ts", + "./translations": "./src/tools/translations/index.ts", + "./run-checks": "./src/tools/run-checks/index.ts", + "./deps-upgrade-advisor": "./src/tools/deps-upgrade-advisor/index.ts" }, "scripts": { "clean": "rm -rf .turbo node_modules", "format": "prettier --check \"**/*.{mjs,ts,md,json}\"", - "build": "tsc", - "build:watch": "tsc --watch" + "build": "tsup", + "build:watch": "tsup --watch", + "test:unit": "vitest run" }, "devDependencies": { + "@kit/email-templates": "workspace:*", "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", "@modelcontextprotocol/sdk": "1.26.0", "@types/node": "catalog:", "postgres": "3.4.8", + "tsup": "catalog:", + "typescript": "^5.9.3", + "vitest": "^4.0.18", "zod": "catalog:" }, - "prettier": "@kit/prettier-config", - "typesVersions": { - "*": { - "*": [ - "src/*" - ] - } - } + "prettier": "@kit/prettier-config" } diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 88071a631..c49e95f36 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -6,10 +6,20 @@ import { registerDatabaseResources, registerDatabaseTools, } from './tools/database'; +import { registerKitDbTools } from './tools/db/index'; +import { registerDepsUpgradeAdvisorTool } from './tools/deps-upgrade-advisor/index'; +import { registerKitDevTools } from './tools/dev/index'; +import { registerKitEmailTemplatesTools } from './tools/emails/index'; +import { registerKitEnvTools } from './tools/env/index'; +import { registerKitEmailsTools } from './tools/mailbox/index'; import { registerGetMigrationsTools } from './tools/migrations'; import { registerPRDTools } from './tools/prd-manager'; +import { registerKitPrerequisitesTool } from './tools/prerequisites/index'; import { registerPromptsSystem } from './tools/prompts'; +import { registerRunChecksTool } from './tools/run-checks/index'; import { registerScriptsTools } from './tools/scripts'; +import { registerKitStatusTool } from './tools/status/index'; +import { registerKitTranslationsTools } from './tools/translations/index'; async function main() { // Create server instance @@ -21,10 +31,20 @@ async function main() { const transport = new StdioServerTransport(); registerGetMigrationsTools(server); + registerKitStatusTool(server); + registerKitPrerequisitesTool(server); + registerKitEnvTools(server); + registerKitDevTools(server); + registerKitDbTools(server); + registerKitEmailsTools(server); + registerKitEmailTemplatesTools(server); + registerKitTranslationsTools(server); registerDatabaseTools(server); registerDatabaseResources(server); registerComponentsTools(server); registerScriptsTools(server); + registerRunChecksTool(server); + registerDepsUpgradeAdvisorTool(server); registerPRDTools(server); registerPromptsSystem(server); diff --git a/packages/mcp-server/src/tools/components.ts b/packages/mcp-server/src/tools/components.ts index 91de3568f..74a0dece2 100644 --- a/packages/mcp-server/src/tools/components.ts +++ b/packages/mcp-server/src/tools/components.ts @@ -1,7 +1,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { z } from 'zod'; +import { z } from 'zod/v3'; interface ComponentInfo { name: string; @@ -345,9 +345,12 @@ export function registerComponentsTools(server: McpServer) { } function createGetComponentsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_components', - 'Get all available UI components from the @kit/ui package with descriptions', + { + description: + 'Get all available UI components from the @kit/ui package with descriptions', + }, async () => { const components = await ComponentsTool.getComponents(); @@ -371,13 +374,15 @@ function createGetComponentsTool(server: McpServer) { } function createGetComponentContentTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_component_content', - 'Get the source code content of a specific UI component', { - state: z.object({ - componentName: z.string(), - }), + description: 'Get the source code content of a specific UI component', + inputSchema: { + state: z.object({ + componentName: z.string(), + }), + }, }, async ({ state }) => { const content = await ComponentsTool.getComponentContent( @@ -397,13 +402,16 @@ function createGetComponentContentTool(server: McpServer) { } function createComponentsSearchTool(server: McpServer) { - return server.tool( + return server.registerTool( 'components_search', - 'Search UI components by keyword in name, description, or category', { - state: z.object({ - query: z.string(), - }), + description: + 'Search UI components by keyword in name, description, or category', + inputSchema: { + state: z.object({ + query: z.string(), + }), + }, }, async ({ state }) => { const components = await ComponentsTool.searchComponents(state.query); @@ -439,13 +447,16 @@ function createComponentsSearchTool(server: McpServer) { } function createGetComponentPropsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_component_props', - 'Extract component props, interfaces, and variants from a UI component', { - state: z.object({ - componentName: z.string(), - }), + description: + 'Extract component props, interfaces, and variants from a UI component', + inputSchema: { + state: z.object({ + componentName: z.string(), + }), + }, }, async ({ state }) => { const propsInfo = await ComponentsTool.getComponentProps( diff --git a/packages/mcp-server/src/tools/database.ts b/packages/mcp-server/src/tools/database.ts index 9a424af03..e5c2eda28 100644 --- a/packages/mcp-server/src/tools/database.ts +++ b/packages/mcp-server/src/tools/database.ts @@ -2,7 +2,7 @@ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { readFile, readdir, stat } from 'node:fs/promises'; import { join } from 'node:path'; import postgres from 'postgres'; -import { z } from 'zod'; +import { z } from 'zod/v3'; const DATABASE_URL = process.env.DATABASE_URL || @@ -1135,9 +1135,12 @@ export function registerDatabaseResources(server: McpServer) { } function createGetSchemaFilesTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_schema_files', - '🔥 DATABASE SCHEMA FILES (SOURCE OF TRUTH - ALWAYS CURRENT) - Use these over migrations!', + { + description: + '🔥 DATABASE SCHEMA FILES (SOURCE OF TRUTH - ALWAYS CURRENT) - Use these over migrations!', + }, async () => { const schemaFiles = await DatabaseTool.getSchemaFiles(); @@ -1168,9 +1171,12 @@ function createGetSchemaFilesTool(server: McpServer) { } function createGetFunctionsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_database_functions', - 'Get all database functions with descriptions and usage guidance', + { + description: + 'Get all database functions with descriptions and usage guidance', + }, async () => { const functions = await DatabaseTool.getFunctions(); @@ -1202,13 +1208,16 @@ function createGetFunctionsTool(server: McpServer) { } function createGetFunctionDetailsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_function_details', - 'Get detailed information about a specific database function', { - state: z.object({ - functionName: z.string(), - }), + description: + 'Get detailed information about a specific database function', + inputSchema: { + state: z.object({ + functionName: z.string(), + }), + }, }, async ({ state }) => { const func = await DatabaseTool.getFunctionDetails(state.functionName); @@ -1253,13 +1262,15 @@ Source File: ${func.sourceFile}`, } function createSearchFunctionsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'search_database_functions', - 'Search database functions by name, description, or purpose', { - state: z.object({ - query: z.string(), - }), + description: 'Search database functions by name, description, or purpose', + inputSchema: { + state: z.object({ + query: z.string(), + }), + }, }, async ({ state }) => { const functions = await DatabaseTool.searchFunctions(state.query); @@ -1295,13 +1306,16 @@ function createSearchFunctionsTool(server: McpServer) { } function createGetSchemaContentTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_schema_content', - '📋 Get raw schema file content (CURRENT DATABASE STATE) - Source of truth for database structure', { - state: z.object({ - fileName: z.string(), - }), + description: + '📋 Get raw schema file content (CURRENT DATABASE STATE) - Source of truth for database structure', + inputSchema: { + state: z.object({ + fileName: z.string(), + }), + }, }, async ({ state }) => { const content = await DatabaseTool.getSchemaContent(state.fileName); @@ -1319,13 +1333,16 @@ function createGetSchemaContentTool(server: McpServer) { } function createGetSchemasByTopicTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_schemas_by_topic', - '🎯 Find schema files by topic (accounts, auth, billing, permissions, etc.) - Fastest way to find relevant schemas', { - state: z.object({ - topic: z.string(), - }), + description: + '🎯 Find schema files by topic (accounts, auth, billing, permissions, etc.) - Fastest way to find relevant schemas', + inputSchema: { + state: z.object({ + topic: z.string(), + }), + }, }, async ({ state }) => { const schemas = await DatabaseTool.getSchemasByTopic(state.topic); @@ -1368,13 +1385,16 @@ function createGetSchemasByTopicTool(server: McpServer) { } function createGetSchemaBySectionTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_schema_by_section', - '📂 Get specific schema by section name (Accounts, Permissions, etc.) - Direct access to schema sections', { - state: z.object({ - section: z.string(), - }), + description: + '📂 Get specific schema by section name (Accounts, Permissions, etc.) - Direct access to schema sections', + inputSchema: { + state: z.object({ + section: z.string(), + }), + }, }, async ({ state }) => { const schema = await DatabaseTool.getSchemaBySection(state.section); @@ -1414,9 +1434,12 @@ function createGetSchemaBySectionTool(server: McpServer) { } function createDatabaseSummaryTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_database_summary', - '📊 Get comprehensive database overview with tables, enums, and functions', + { + description: + '📊 Get comprehensive database overview with tables, enums, and functions', + }, async () => { const tables = await DatabaseTool.getAllProjectTables(); const enums = await DatabaseTool.getAllEnums(); @@ -1468,9 +1491,11 @@ function createDatabaseSummaryTool(server: McpServer) { } function createDatabaseTablesListTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_database_tables', - '📋 Get list of all project-defined database tables', + { + description: '📋 Get list of all project-defined database tables', + }, async () => { const tables = await DatabaseTool.getAllProjectTables(); @@ -1487,14 +1512,17 @@ function createDatabaseTablesListTool(server: McpServer) { } function createGetTableInfoTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_table_info', - '🗂️ Get detailed table schema with columns, foreign keys, and indexes', { - state: z.object({ - schema: z.string().default('public'), - tableName: z.string(), - }), + description: + '🗂️ Get detailed table schema with columns, foreign keys, and indexes', + inputSchema: { + state: z.object({ + schema: z.string().default('public'), + tableName: z.string(), + }), + }, }, async ({ state }) => { try { @@ -1526,13 +1554,15 @@ function createGetTableInfoTool(server: McpServer) { } function createGetEnumInfoTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_enum_info', - '🏷️ Get enum type definition with all possible values', { - state: z.object({ - enumName: z.string(), - }), + description: '🏷️ Get enum type definition with all possible values', + inputSchema: { + state: z.object({ + enumName: z.string(), + }), + }, }, async ({ state }) => { try { @@ -1573,9 +1603,11 @@ function createGetEnumInfoTool(server: McpServer) { } function createGetAllEnumsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_all_enums', - '🏷️ Get all enum types and their values', + { + description: '🏷️ Get all enum types and their values', + }, async () => { try { const enums = await DatabaseTool.getAllEnums(); diff --git a/packages/mcp-server/src/tools/db/__tests__/kit-db.service.test.ts b/packages/mcp-server/src/tools/db/__tests__/kit-db.service.test.ts new file mode 100644 index 000000000..52e4ee7a4 --- /dev/null +++ b/packages/mcp-server/src/tools/db/__tests__/kit-db.service.test.ts @@ -0,0 +1,248 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { type KitDbServiceDeps, createKitDbService } from '../kit-db.service'; + +function createDeps( + overrides: Partial = {}, +): KitDbServiceDeps { + return { + rootPath: '/repo', + async resolveVariantContext() { + return { + variant: 'next-supabase', + variantFamily: 'supabase', + tool: 'supabase', + }; + }, + async executeCommand() { + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async isPortOpen() { + return false; + }, + async fileExists() { + return false; + }, + async readdir() { + return []; + }, + async readJsonFile() { + return {}; + }, + ...overrides, + }; +} + +describe('KitDbService.status', () => { + it('reports pending migrations when CLI output is unavailable', async () => { + const deps = createDeps({ + async fileExists(path: string) { + return path.includes('supabase/migrations'); + }, + async readdir() { + return ['20260101010101_create_table.sql']; + }, + async executeCommand() { + throw new Error('supabase missing'); + }, + }); + + const service = createKitDbService(deps); + const result = await service.status(); + + expect(result.migrations.pending).toBe(1); + expect(result.migrations.pending_names).toEqual([ + '20260101010101_create_table', + ]); + }); + + it('treats local migrations as applied when connected', async () => { + const deps = createDeps({ + async fileExists(path: string) { + return path.includes('supabase/migrations'); + }, + async readdir() { + return ['20260101010101_create_table.sql']; + }, + async executeCommand() { + throw new Error('supabase missing'); + }, + async isPortOpen() { + return true; + }, + }); + + const service = createKitDbService(deps); + const result = await service.status(); + + expect(result.migrations.applied).toBe(1); + expect(result.migrations.pending).toBe(0); + }); + + it('parses supabase migrations list output', async () => { + const deps = createDeps({ + async executeCommand(command, args) { + if (command === 'supabase' && args.join(' ') === 'migrations list') { + return { + stdout: + '20260101010101_create_table | applied\n20260202020202_add_billing | not applied\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }); + + const service = createKitDbService(deps); + const result = await service.status(); + + expect(result.migrations.applied).toBe(1); + expect(result.migrations.pending).toBe(1); + expect(result.migrations.pending_names).toEqual([ + '20260202020202_add_billing', + ]); + }); + + it('maps id/name columns to local migration names', async () => { + const deps = createDeps({ + async fileExists(path: string) { + return path.includes('supabase/migrations'); + }, + async readdir() { + return ['20240319163440_roles-seed.sql']; + }, + async executeCommand(command, args) { + if (command === 'supabase' && args.join(' ') === 'migrations list') { + return { + stdout: + '20240319163440 | roles-seed | Applied\n20240401010101 | add-billing | Pending\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }); + + const service = createKitDbService(deps); + const result = await service.status(); + + expect(result.migrations.applied).toBe(1); + expect(result.migrations.pending).toBe(1); + expect(result.migrations.pending_names).toEqual([ + '20240401010101_add-billing', + ]); + }); + + it('treats id/name list with no status as applied', async () => { + const deps = createDeps({ + async fileExists(path: string) { + return path.includes('supabase/migrations'); + }, + async readdir() { + return [ + '20240319163440_roles-seed.sql', + '20240401010101_add-billing.sql', + ]; + }, + async executeCommand(command, args) { + if (command === 'supabase' && args.join(' ') === 'migrations list') { + return { + stdout: + '20240319163440 | roles-seed\n20240401010101 | add-billing\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }); + + const service = createKitDbService(deps); + const result = await service.status(); + + expect(result.migrations.applied).toBe(2); + expect(result.migrations.pending).toBe(0); + }); +}); + +describe('KitDbService.migrate', () => { + it('throws when target is not latest', async () => { + const service = createKitDbService(createDeps()); + + await expect( + service.migrate({ target: '20260101010101_create_table' }), + ).rejects.toThrow(/target "latest"/); + }); + + it('returns applied migrations from pending list', async () => { + const deps = createDeps({ + async fileExists(path: string) { + return path.includes('supabase/migrations'); + }, + async readdir() { + return ['20260101010101_create_table.sql']; + }, + async executeCommand(command, args) { + if (command === 'supabase' && args.join(' ') === 'migrations list') { + return { + stdout: '20260101010101_create_table | pending\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }); + + const service = createKitDbService(deps); + const result = await service.migrate({ target: 'latest' }); + + expect(result.applied).toEqual(['20260101010101_create_table']); + expect(result.total_applied).toBe(1); + }); +}); + +describe('KitDbService.reset', () => { + it('requires confirm true', async () => { + const service = createKitDbService(createDeps()); + + await expect(service.reset({ confirm: false })).rejects.toThrow( + /confirm: true/, + ); + }); +}); + +describe('KitDbService.seed', () => { + it('uses db:seed script when available', async () => { + const exec = vi.fn(async (_command: string, _args: string[]) => ({ + stdout: '', + stderr: '', + exitCode: 0, + })); + + const deps = createDeps({ + async readJsonFile() { + return { scripts: { 'db:seed': 'tsx scripts/seed.ts' } }; + }, + async executeCommand(command, args) { + return exec(command, args); + }, + }); + + const service = createKitDbService(deps); + await service.seed(); + + expect(exec).toHaveBeenCalledWith('pnpm', [ + '--filter', + 'web', + 'run', + 'db:seed', + ]); + }); +}); diff --git a/packages/mcp-server/src/tools/db/index.ts b/packages/mcp-server/src/tools/db/index.ts new file mode 100644 index 000000000..9675628ec --- /dev/null +++ b/packages/mcp-server/src/tools/db/index.ts @@ -0,0 +1,365 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { access, readFile, readdir } from 'node:fs/promises'; +import { Socket } from 'node:net'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { type KitDbServiceDeps, createKitDbService } from './kit-db.service'; +import { + KitDbMigrateInputSchema, + KitDbMigrateOutputSchema, + KitDbResetInputSchema, + KitDbResetOutputSchema, + KitDbSeedInputSchema, + KitDbSeedOutputSchema, + KitDbStatusInputSchema, + KitDbStatusOutputSchema, +} from './schema'; + +const execFileAsync = promisify(execFile); + +type TextContent = { + type: 'text'; + text: string; +}; + +export function registerKitDbTools(server: McpServer) { + const service = createKitDbService(createKitDbDeps()); + + server.registerTool( + 'kit_db_status', + { + description: 'Check database connectivity and migrations state', + inputSchema: KitDbStatusInputSchema, + outputSchema: KitDbStatusOutputSchema, + }, + async (input) => { + KitDbStatusInputSchema.parse(input); + + try { + const result = await service.status(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_db_status', error); + } + }, + ); + + server.registerTool( + 'kit_db_migrate', + { + description: 'Apply pending database migrations', + inputSchema: KitDbMigrateInputSchema, + outputSchema: KitDbMigrateOutputSchema, + }, + async (input) => { + try { + const parsed = KitDbMigrateInputSchema.parse(input); + const result = await service.migrate(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_db_migrate', error); + } + }, + ); + + server.registerTool( + 'kit_db_seed', + { + description: 'Run database seed scripts', + inputSchema: KitDbSeedInputSchema, + outputSchema: KitDbSeedOutputSchema, + }, + async (input) => { + KitDbSeedInputSchema.parse(input); + + try { + const result = await service.seed(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_db_seed', error); + } + }, + ); + + server.registerTool( + 'kit_db_reset', + { + description: 'Reset the database after confirmation', + inputSchema: KitDbResetInputSchema, + outputSchema: KitDbResetOutputSchema, + }, + async (input) => { + try { + const parsed = KitDbResetInputSchema.parse(input); + const result = await service.reset(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_db_reset', error); + } + }, + ); +} + +export function createKitDbDeps(rootPath = process.cwd()): KitDbServiceDeps { + return { + rootPath, + async resolveVariantContext() { + const configuredVariant = await readConfiguredVariant(rootPath); + if (configuredVariant) { + return mapVariant(configuredVariant); + } + + if (await pathExists(join(rootPath, 'apps', 'web', 'supabase'))) { + return mapVariant('next-supabase'); + } + + const webPackage = await readJsonIfPresent( + join(rootPath, 'apps', 'web', 'package.json'), + ); + const dependencies = { + ...(webPackage?.dependencies ?? {}), + ...(webPackage?.devDependencies ?? {}), + } as Record; + + if ('prisma' in dependencies || '@prisma/client' in dependencies) { + return mapVariant('next-prisma'); + } + + if ('drizzle-kit' in dependencies || 'drizzle-orm' in dependencies) { + return mapVariant('next-drizzle'); + } + + return mapVariant('next-supabase'); + }, + async executeCommand(command: string, args: string[]) { + const result = await executeWithFallback(rootPath, command, args); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + async isPortOpen(port: number) { + return checkPort(port); + }, + async fileExists(path: string) { + return pathExists(path); + }, + async readdir(path: string) { + return readdir(path); + }, + async readJsonFile(path: string) { + const raw = await readFile(path, 'utf8'); + return JSON.parse(raw) as unknown; + }, + }; +} + +function mapVariant(variant: string) { + if (variant === 'next-prisma') { + return { + variant, + variantFamily: 'orm', + tool: 'prisma', + } as const; + } + + if (variant === 'next-drizzle') { + return { + variant, + variantFamily: 'orm', + tool: 'drizzle-kit', + } as const; + } + + if (variant === 'react-router-supabase') { + return { + variant, + variantFamily: 'supabase', + tool: 'supabase', + } as const; + } + + return { + variant: variant.includes('prisma') ? variant : 'next-supabase', + variantFamily: 'supabase', + tool: 'supabase', + } as const; +} + +async function readConfiguredVariant(rootPath: string) { + const configPath = join(rootPath, '.makerkit', 'config.json'); + + try { + await access(configPath); + const config = JSON.parse(await readFile(configPath, 'utf8')) as Record< + string, + unknown + >; + + return ( + readString(config, 'variant') ?? + readString(config, 'template') ?? + readString(config, 'kitVariant') + ); + } catch { + return null; + } +} + +function readString(obj: Record, key: string) { + const value = obj[key]; + return typeof value === 'string' && value.length > 0 ? value : null; +} + +async function executeWithFallback( + rootPath: string, + command: string, + args: string[], +) { + try { + return await execFileAsync(command, args, { + cwd: rootPath, + }); + } catch (error) { + if (isLocalCliCandidate(command)) { + const localBinCandidates = [ + join(rootPath, 'node_modules', '.bin', command), + join(rootPath, 'apps', 'web', 'node_modules', '.bin', command), + ]; + + for (const localBin of localBinCandidates) { + try { + return await execFileAsync(localBin, args, { + cwd: rootPath, + }); + } catch { + // Try next local binary candidate. + } + } + + try { + return await execFileAsync('pnpm', ['exec', command, ...args], { + cwd: rootPath, + }); + } catch { + return execFileAsync( + 'pnpm', + ['--filter', 'web', 'exec', command, ...args], + { + cwd: rootPath, + }, + ); + } + } + + if (command === 'pnpm' || command === 'docker') { + return execFileAsync(command, args, { + cwd: rootPath, + }); + } + + throw error; + } +} + +function isLocalCliCandidate(command: string) { + return ( + command === 'supabase' || command === 'drizzle-kit' || command === 'prisma' + ); +} + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function readJsonIfPresent(path: string) { + try { + const content = await readFile(path, 'utf8'); + return JSON.parse(content) as { + dependencies?: Record; + devDependencies?: Record; + }; + } catch { + return null; + } +} + +async function checkPort(port: number) { + return new Promise((resolve) => { + const socket = new Socket(); + + socket.setTimeout(200); + + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.once('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.once('error', () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, '127.0.0.1'); + }); +} + +function buildErrorResponse(tool: string, error: unknown) { + const message = `${tool} failed: ${toErrorMessage(error)}`; + + return { + isError: true, + structuredContent: { + error: { + message, + }, + }, + content: buildTextContent(message), + }; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +function buildTextContent(text: string): TextContent[] { + return [{ type: 'text', text }]; +} + +export { createKitDbService } from './kit-db.service'; +export type { KitDbServiceDeps } from './kit-db.service'; +export type { KitDbStatusOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/db/kit-db.service.ts b/packages/mcp-server/src/tools/db/kit-db.service.ts new file mode 100644 index 000000000..754eb39db --- /dev/null +++ b/packages/mcp-server/src/tools/db/kit-db.service.ts @@ -0,0 +1,505 @@ +import { join } from 'node:path'; + +import type { + DbTool, + KitDbMigrateInput, + KitDbMigrateOutput, + KitDbResetInput, + KitDbResetOutput, + KitDbSeedOutput, + KitDbStatusOutput, +} from './schema'; + +type VariantFamily = 'supabase' | 'orm'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface VariantContext { + variant: string; + variantFamily: VariantFamily; + tool: DbTool; +} + +interface MigrationStatus { + applied: string[]; + pending: string[]; +} + +interface SeedScript { + command: string; + args: string[]; +} + +export interface KitDbServiceDeps { + rootPath: string; + resolveVariantContext(): Promise; + executeCommand(command: string, args: string[]): Promise; + isPortOpen(port: number): Promise; + fileExists(path: string): Promise; + readdir(path: string): Promise; + readJsonFile(path: string): Promise; +} + +const SUPABASE_PORT = 54321; +const ORM_PORT = 5432; + +export function createKitDbService(deps: KitDbServiceDeps) { + return new KitDbService(deps); +} + +export class KitDbService { + constructor(private readonly deps: KitDbServiceDeps) {} + + async status(): Promise { + const variant = await this.deps.resolveVariantContext(); + const connected = await this.isConnected(variant); + const migrations = await this.getMigrationSummary(variant, { + connected, + }); + + return { + connected, + tool: variant.tool, + migrations: { + applied: migrations.applied.length, + pending: migrations.pending.length, + pending_names: migrations.pending, + }, + }; + } + + async migrate(input: KitDbMigrateInput): Promise { + const variant = await this.deps.resolveVariantContext(); + + if (input.target !== 'latest') { + throw new Error( + `Specific migration targets are not supported for ${variant.tool} in this kit. Use target "latest".`, + ); + } + + const pending = await this.getPendingMigrationNames(variant); + + await this.runMigrations(variant); + + return { + applied: pending, + total_applied: pending.length, + status: 'success', + }; + } + + async seed(): Promise { + const variant = await this.deps.resolveVariantContext(); + const seedScript = await this.resolveSeedScript(variant); + + await this.deps.executeCommand(seedScript.command, seedScript.args); + + return { + status: 'success', + message: 'Seed data applied successfully', + }; + } + + async reset(input: KitDbResetInput): Promise { + if (!input.confirm) { + throw new Error('Database reset requires confirm: true'); + } + + const variant = await this.deps.resolveVariantContext(); + + if (variant.variantFamily === 'supabase') { + await this.deps.executeCommand('supabase', ['db', 'reset']); + } else { + await this.deps.executeCommand('docker', ['compose', 'down', '-v']); + await this.deps.executeCommand('docker', [ + 'compose', + 'up', + '-d', + 'postgres', + ]); + await this.runMigrations(variant); + } + + return { + status: 'success', + message: 'Database reset and migrations re-applied', + }; + } + + private async isConnected(variant: VariantContext) { + const port = + variant.variantFamily === 'supabase' ? SUPABASE_PORT : ORM_PORT; + return this.deps.isPortOpen(port); + } + + private async getMigrationSummary( + variant: VariantContext, + options: { + connected?: boolean; + } = {}, + ): Promise { + const localMigrations = await this.listLocalMigrations(variant); + + if (variant.variantFamily === 'supabase') { + const parsed = await this.tryParseSupabaseMigrations(localMigrations); + if (parsed) { + return parsed; + } + } + + if ( + variant.variantFamily === 'supabase' && + options.connected && + localMigrations.length > 0 + ) { + return { + applied: localMigrations, + pending: [], + }; + } + + return { + applied: [], + pending: localMigrations, + }; + } + + private async getPendingMigrationNames(variant: VariantContext) { + const summary = await this.getMigrationSummary(variant); + return summary.pending; + } + + private async runMigrations(variant: VariantContext) { + if (variant.tool === 'supabase') { + await this.deps.executeCommand('supabase', ['db', 'push']); + return; + } + + if (variant.tool === 'drizzle-kit') { + await this.deps.executeCommand('drizzle-kit', ['push']); + return; + } + + await this.deps.executeCommand('prisma', ['db', 'push']); + } + + private async resolveSeedScript( + variant: VariantContext, + ): Promise { + const customScript = await this.findSeedScript(); + + if (customScript) { + return { + command: 'pnpm', + args: ['--filter', 'web', 'run', customScript], + }; + } + + if (variant.tool === 'supabase') { + return { + command: 'supabase', + args: ['db', 'seed'], + }; + } + + if (variant.tool === 'prisma') { + return { + command: 'prisma', + args: ['db', 'seed'], + }; + } + + throw new Error( + 'No seed command configured. Add a db:seed or seed script to apps/web/package.json.', + ); + } + + private async findSeedScript() { + const packageJsonPath = join( + this.deps.rootPath, + 'apps', + 'web', + 'package.json', + ); + + const packageJson = await this.readObject(packageJsonPath); + const scripts = this.readObjectValue(packageJson, 'scripts'); + + if (this.readString(scripts, 'db:seed')) { + return 'db:seed'; + } + + if (this.readString(scripts, 'seed')) { + return 'seed'; + } + + return null; + } + + private async listLocalMigrations(variant: VariantContext) { + const migrationsDir = await this.resolveMigrationsDir(variant); + + if (!migrationsDir) { + return []; + } + + const entries = await this.deps.readdir(migrationsDir); + return this.filterMigrationNames(variant, entries); + } + + private async resolveMigrationsDir(variant: VariantContext) { + if (variant.tool === 'supabase') { + const supabaseDir = join( + this.deps.rootPath, + 'apps', + 'web', + 'supabase', + 'migrations', + ); + return (await this.deps.fileExists(supabaseDir)) ? supabaseDir : null; + } + + if (variant.tool === 'prisma') { + const prismaDir = join( + this.deps.rootPath, + 'apps', + 'web', + 'prisma', + 'migrations', + ); + return (await this.deps.fileExists(prismaDir)) ? prismaDir : null; + } + + const drizzleDir = join( + this.deps.rootPath, + 'apps', + 'web', + 'drizzle', + 'migrations', + ); + + if (await this.deps.fileExists(drizzleDir)) { + return drizzleDir; + } + + const fallbackDir = join(this.deps.rootPath, 'drizzle', 'migrations'); + return (await this.deps.fileExists(fallbackDir)) ? fallbackDir : null; + } + + private filterMigrationNames(variant: VariantContext, entries: string[]) { + if (variant.tool === 'prisma') { + return entries.filter((entry) => entry.trim().length > 0); + } + + return entries + .filter((entry) => entry.endsWith('.sql')) + .map((entry) => entry.replace(/\.sql$/, '')); + } + + private async tryParseSupabaseMigrations(localMigrations: string[]) { + try { + const localResult = await this.deps.executeCommand('supabase', [ + 'migrations', + 'list', + '--local', + ]); + const parsedLocal = parseSupabaseMigrationsList( + localResult.stdout, + localMigrations, + ); + if (parsedLocal) { + return parsedLocal; + } + } catch { + // Fall through to remote attempt. + } + + try { + const remoteResult = await this.deps.executeCommand('supabase', [ + 'migrations', + 'list', + ]); + return parseSupabaseMigrationsList(remoteResult.stdout, localMigrations); + } catch { + return null; + } + } + + private async readObject(path: string): Promise> { + try { + const value = await this.deps.readJsonFile(path); + + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + + return value as Record; + } catch { + return {}; + } + } + + private readObjectValue(obj: Record, key: string) { + const value = obj[key]; + + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + + return value as Record; + } + + private readString(obj: Record, key: string) { + const value = obj[key]; + return typeof value === 'string' && value.length > 0 ? value : null; + } +} + +function parseSupabaseMigrationsList( + output: string, + localMigrations: string[], +): MigrationStatus | null { + const applied = new Set(); + const pending = new Set(); + const appliedCandidates = new Set(); + const lines = output.split('\n'); + const migrationsById = buildMigrationIdMap(localMigrations); + let sawStatus = false; + let sawId = false; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + + const status = extractSupabaseStatus(trimmed); + const nameFromLine = extractMigrationName( + trimmed, + localMigrations, + migrationsById, + ); + + if (nameFromLine) { + sawId = true; + } + + if (!status) { + if (nameFromLine) { + appliedCandidates.add(nameFromLine); + } + continue; + } + + sawStatus = true; + + if (!nameFromLine) { + continue; + } + + if (status === 'applied') { + applied.add(nameFromLine); + } else { + pending.add(nameFromLine); + } + } + + if (!sawStatus && sawId && appliedCandidates.size > 0) { + const appliedList = Array.from(appliedCandidates); + const pendingList = localMigrations.filter( + (migration) => !appliedCandidates.has(migration), + ); + + return { + applied: appliedList, + pending: pendingList, + }; + } + + if (applied.size === 0 && pending.size === 0) { + return null; + } + + return { + applied: Array.from(applied), + pending: Array.from(pending), + }; +} + +function extractMigrationName( + line: string, + candidates: string[], + migrationsById: Map, +) { + const directMatch = line.match(/\b\d{14}_[a-z0-9_]+\b/i); + if (directMatch?.[0]) { + return directMatch[0]; + } + + const columns = line + .split('|') + .map((value) => value.trim()) + .filter((value) => value.length > 0); + + if (columns.length >= 2) { + const id = columns.find((value) => /^\d{14}$/.test(value)); + if (id) { + const byId = migrationsById.get(id); + if (byId) { + return byId; + } + + const nameColumn = columns[1]; + const normalizedName = normalizeMigrationName(nameColumn); + const candidate = `${id}_${normalizedName}`; + const exactMatch = candidates.find( + (migration) => + migration.toLowerCase() === candidate.toLowerCase() || + normalizeMigrationName(migration) === normalizedName, + ); + + return exactMatch ?? candidate; + } + } + + return candidates.find((name) => line.includes(name)) ?? null; +} + +function extractSupabaseStatus(line: string) { + const lower = line.toLowerCase(); + + if (/\b(not applied|pending|missing)\b/.test(lower)) { + return 'pending'; + } + + if (/\b(applied|completed)\b/.test(lower)) { + return 'applied'; + } + + return null; +} + +function buildMigrationIdMap(migrations: string[]) { + const map = new Map(); + + for (const migration of migrations) { + const match = migration.match(/^(\d{14})_(.+)$/); + if (match?.[1]) { + map.set(match[1], migration); + } + } + + return map; +} + +function normalizeMigrationName(value: string) { + return value + .trim() + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/[^a-z0-9_-]/g, ''); +} diff --git a/packages/mcp-server/src/tools/db/schema.ts b/packages/mcp-server/src/tools/db/schema.ts new file mode 100644 index 000000000..71fd64101 --- /dev/null +++ b/packages/mcp-server/src/tools/db/schema.ts @@ -0,0 +1,53 @@ +import { z } from 'zod/v3'; + +const DbToolSchema = z.enum(['supabase', 'drizzle-kit', 'prisma']); + +const MigrationStatusSchema = z.object({ + applied: z.number(), + pending: z.number(), + pending_names: z.array(z.string()), +}); + +export const KitDbStatusInputSchema = z.object({}); + +export const KitDbStatusOutputSchema = z.object({ + connected: z.boolean(), + tool: DbToolSchema, + migrations: MigrationStatusSchema, +}); + +export const KitDbMigrateInputSchema = z.object({ + target: z.string().default('latest'), +}); + +export const KitDbMigrateOutputSchema = z.object({ + applied: z.array(z.string()), + total_applied: z.number(), + status: z.literal('success'), +}); + +export const KitDbSeedInputSchema = z.object({}); + +export const KitDbSeedOutputSchema = z.object({ + status: z.literal('success'), + message: z.string(), +}); + +export const KitDbResetInputSchema = z.object({ + confirm: z.boolean().default(false), +}); + +export const KitDbResetOutputSchema = z.object({ + status: z.literal('success'), + message: z.string(), +}); + +export type DbTool = z.infer; +export type KitDbStatusInput = z.infer; +export type KitDbStatusOutput = z.infer; +export type KitDbMigrateInput = z.infer; +export type KitDbMigrateOutput = z.infer; +export type KitDbSeedInput = z.infer; +export type KitDbSeedOutput = z.infer; +export type KitDbResetInput = z.infer; +export type KitDbResetOutput = z.infer; diff --git a/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.service.test.ts b/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.service.test.ts new file mode 100644 index 000000000..f14fdb4bd --- /dev/null +++ b/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.service.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it } from 'vitest'; + +import { + type DepsUpgradeAdvisorDeps, + createDepsUpgradeAdvisorService, +} from '../deps-upgrade-advisor.service'; + +function createDeps( + output: unknown, + overrides: Partial = {}, +): DepsUpgradeAdvisorDeps { + return { + async executeCommand() { + return { + stdout: JSON.stringify(output), + stderr: '', + exitCode: 0, + }; + }, + nowIso() { + return '2026-02-09T00:00:00.000Z'; + }, + ...overrides, + }; +} + +describe('DepsUpgradeAdvisorService', () => { + it('flags major updates as potentially breaking', async () => { + const service = createDepsUpgradeAdvisorService( + createDeps([ + { + name: 'zod', + current: '3.25.0', + wanted: '3.26.0', + latest: '4.0.0', + workspace: 'root', + dependencyType: 'dependencies', + }, + ]), + ); + + const result = await service.advise({}); + const zod = result.recommendations.find((item) => item.package === 'zod'); + + expect(zod?.update_type).toBe('major'); + expect(zod?.potentially_breaking).toBe(true); + expect(zod?.risk).toBe('high'); + }); + + it('prefers wanted for major updates when includeMajor is false', async () => { + const service = createDepsUpgradeAdvisorService( + createDeps([ + { + name: 'example-lib', + current: '1.2.0', + wanted: '1.9.0', + latest: '2.1.0', + workspace: 'root', + dependencyType: 'dependencies', + }, + ]), + ); + + const result = await service.advise({}); + const item = result.recommendations[0]; + + expect(item?.recommended_target).toBe('1.9.0'); + }); + + it('filters out dev dependencies when requested', async () => { + const service = createDepsUpgradeAdvisorService( + createDeps([ + { + name: 'vitest', + current: '2.1.0', + wanted: '2.1.8', + latest: '2.1.8', + workspace: 'root', + dependencyType: 'devDependencies', + }, + { + name: 'zod', + current: '3.25.0', + wanted: '3.25.1', + latest: '3.25.1', + workspace: 'root', + dependencyType: 'dependencies', + }, + ]), + ); + + const result = await service.advise({ + state: { includeDevDependencies: false }, + }); + + expect(result.recommendations).toHaveLength(1); + expect(result.recommendations[0]?.package).toBe('zod'); + }); +}); diff --git a/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.tool.test.ts b/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.tool.test.ts new file mode 100644 index 000000000..e333466a5 --- /dev/null +++ b/packages/mcp-server/src/tools/deps-upgrade-advisor/__tests__/deps-upgrade-advisor.tool.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; + +import { registerDepsUpgradeAdvisorToolWithDeps } from '../index'; +import { DepsUpgradeAdvisorOutputSchema } from '../schema'; + +interface RegisteredTool { + name: string; + handler: (input: unknown) => Promise>; +} + +describe('registerDepsUpgradeAdvisorTool', () => { + it('registers deps_upgrade_advisor and returns typed structured output', async () => { + const tools: RegisteredTool[] = []; + + const server = { + registerTool( + name: string, + _config: Record, + handler: (input: unknown) => Promise>, + ) { + tools.push({ name, handler }); + return {}; + }, + }; + + registerDepsUpgradeAdvisorToolWithDeps(server as never, { + async executeCommand() { + return { + stdout: '[]', + stderr: '', + exitCode: 0, + }; + }, + nowIso() { + return '2026-02-09T00:00:00.000Z'; + }, + }); + + expect(tools).toHaveLength(1); + expect(tools[0]?.name).toBe('deps_upgrade_advisor'); + + const result = await tools[0]!.handler({}); + const parsed = DepsUpgradeAdvisorOutputSchema.parse( + result.structuredContent, + ); + + expect(parsed.generated_at).toBeTruthy(); + expect(Array.isArray(parsed.recommendations)).toBe(true); + }); +}); diff --git a/packages/mcp-server/src/tools/deps-upgrade-advisor/deps-upgrade-advisor.service.ts b/packages/mcp-server/src/tools/deps-upgrade-advisor/deps-upgrade-advisor.service.ts new file mode 100644 index 000000000..c5569b4bc --- /dev/null +++ b/packages/mcp-server/src/tools/deps-upgrade-advisor/deps-upgrade-advisor.service.ts @@ -0,0 +1,307 @@ +import type { + DepsUpgradeAdvisorInput, + DepsUpgradeAdvisorOutput, + DepsUpgradeRecommendation, +} from './schema'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface OutdatedDependency { + package: string; + workspace: string; + dependencyType: string; + current: string; + wanted: string; + latest: string; +} + +export interface DepsUpgradeAdvisorDeps { + executeCommand(command: string, args: string[]): Promise; + nowIso(): string; +} + +export function createDepsUpgradeAdvisorService(deps: DepsUpgradeAdvisorDeps) { + return new DepsUpgradeAdvisorService(deps); +} + +export class DepsUpgradeAdvisorService { + constructor(private readonly deps: DepsUpgradeAdvisorDeps) {} + + async advise( + input: DepsUpgradeAdvisorInput, + ): Promise { + const includeMajor = input.state?.includeMajor ?? false; + const maxPackages = input.state?.maxPackages ?? 50; + const includeDevDependencies = input.state?.includeDevDependencies ?? true; + const warnings: string[] = []; + + const outdated = await this.getOutdatedDependencies(warnings); + + const filtered = outdated.filter((item) => { + if (includeDevDependencies) { + return true; + } + + return !item.dependencyType.toLowerCase().includes('dev'); + }); + + const recommendations = filtered + .map((item) => toRecommendation(item, includeMajor)) + .sort(sortRecommendations) + .slice(0, maxPackages); + + const major = recommendations.filter( + (item) => item.update_type === 'major', + ); + const safe = recommendations.filter((item) => item.update_type !== 'major'); + + if (!includeMajor && major.length > 0) { + warnings.push( + `${major.length} major upgrades were excluded from immediate recommendations. Re-run with includeMajor=true to include them.`, + ); + } + + return { + generated_at: this.deps.nowIso(), + summary: { + total_outdated: filtered.length, + recommended_now: recommendations.filter((item) => + includeMajor ? true : item.update_type !== 'major', + ).length, + major_available: filtered + .map((item) => toRecommendation(item, true)) + .filter((item) => item.update_type === 'major').length, + minor_or_patch_available: filtered + .map((item) => toRecommendation(item, true)) + .filter( + (item) => + item.update_type === 'minor' || item.update_type === 'patch', + ).length, + }, + recommendations, + grouped_commands: { + safe_batch_command: buildBatchCommand( + safe.map((item) => `${item.package}@${item.recommended_target}`), + ), + major_batch_command: includeMajor + ? buildBatchCommand( + major.map((item) => `${item.package}@${item.recommended_target}`), + ) + : null, + }, + warnings, + }; + } + + private async getOutdatedDependencies(warnings: string[]) { + const attempts: string[][] = [ + ['outdated', '--recursive', '--format', 'json'], + ['outdated', '--recursive', '--json'], + ]; + + let lastError: Error | null = null; + + for (const args of attempts) { + const result = await this.deps.executeCommand('pnpm', args); + + if (!result.stdout.trim()) { + if (result.exitCode === 0) { + return [] as OutdatedDependency[]; + } + + warnings.push( + `pnpm ${args.join(' ')} returned no JSON output (exit code ${result.exitCode}).`, + ); + lastError = new Error(result.stderr || 'Missing command output'); + continue; + } + + try { + return normalizeOutdatedJson(JSON.parse(result.stdout)); + } catch (error) { + lastError = error instanceof Error ? error : new Error('Invalid JSON'); + } + } + + throw lastError ?? new Error('Unable to retrieve outdated dependencies'); + } +} + +function toRecommendation( + dependency: OutdatedDependency, + includeMajor: boolean, +): DepsUpgradeRecommendation { + const updateType = getUpdateType(dependency.current, dependency.latest); + const risk = + updateType === 'major' ? 'high' : updateType === 'minor' ? 'medium' : 'low'; + const target = + updateType === 'major' && !includeMajor + ? dependency.wanted + : dependency.latest; + + return { + package: dependency.package, + workspace: dependency.workspace, + dependency_type: dependency.dependencyType, + current: dependency.current, + wanted: dependency.wanted, + latest: dependency.latest, + update_type: updateType, + risk, + potentially_breaking: updateType === 'major', + recommended_target: target, + recommended_command: `pnpm up -r ${dependency.package}@${target}`, + reason: + updateType === 'major' && !includeMajor + ? 'Major version available and potentially breaking; recommended target is the highest non-major range match.' + : `Recommended ${updateType} update based on current vs latest version.`, + }; +} + +function normalizeOutdatedJson(value: unknown): OutdatedDependency[] { + if (Array.isArray(value)) { + return value.map(normalizeOutdatedItem).filter((item) => item !== null); + } + + if (isRecord(value)) { + const rows: OutdatedDependency[] = []; + + for (const [workspace, data] of Object.entries(value)) { + if (!isRecord(data)) { + continue; + } + + for (const [name, info] of Object.entries(data)) { + if (!isRecord(info)) { + continue; + } + + const current = readString(info, 'current'); + const wanted = readString(info, 'wanted'); + const latest = readString(info, 'latest'); + + if (!current || !wanted || !latest) { + continue; + } + + rows.push({ + package: name, + workspace, + dependencyType: readString(info, 'dependencyType') ?? 'unknown', + current, + wanted, + latest, + }); + } + } + + return rows; + } + + return []; +} + +function normalizeOutdatedItem(value: unknown): OutdatedDependency | null { + if (!isRecord(value)) { + return null; + } + + const name = + readString(value, 'name') ?? + readString(value, 'package') ?? + readString(value, 'pkgName'); + const current = readString(value, 'current'); + const wanted = readString(value, 'wanted'); + const latest = readString(value, 'latest'); + + if (!name || !current || !wanted || !latest) { + return null; + } + + return { + package: name, + workspace: + readString(value, 'workspace') ?? + readString(value, 'dependent') ?? + readString(value, 'location') ?? + 'root', + dependencyType: + readString(value, 'dependencyType') ?? + readString(value, 'packageType') ?? + 'unknown', + current, + wanted, + latest, + }; +} + +function getUpdateType(current: string, latest: string) { + const currentVersion = parseSemver(current); + const latestVersion = parseSemver(latest); + + if (!currentVersion || !latestVersion) { + return 'unknown' as const; + } + + if (latestVersion.major > currentVersion.major) { + return 'major' as const; + } + + if (latestVersion.minor > currentVersion.minor) { + return 'minor' as const; + } + + if (latestVersion.patch > currentVersion.patch) { + return 'patch' as const; + } + + return 'unknown' as const; +} + +function parseSemver(input: string) { + const match = input.match(/(\d+)\.(\d+)\.(\d+)/); + + if (!match) { + return null; + } + + return { + major: Number(match[1]), + minor: Number(match[2]), + patch: Number(match[3]), + }; +} + +function buildBatchCommand(upgrades: string[]) { + if (upgrades.length === 0) { + return null; + } + + return `pnpm up -r ${upgrades.join(' ')}`; +} + +function sortRecommendations( + a: DepsUpgradeRecommendation, + b: DepsUpgradeRecommendation, +) { + const rank: Record = { + high: 0, + medium: 1, + low: 2, + }; + + return rank[a.risk] - rank[b.risk] || a.package.localeCompare(b.package); +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +function readString(record: Record, key: string) { + const value = record[key]; + return typeof value === 'string' && value.length > 0 ? value : null; +} diff --git a/packages/mcp-server/src/tools/deps-upgrade-advisor/index.ts b/packages/mcp-server/src/tools/deps-upgrade-advisor/index.ts new file mode 100644 index 000000000..fa560b479 --- /dev/null +++ b/packages/mcp-server/src/tools/deps-upgrade-advisor/index.ts @@ -0,0 +1,122 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +import { + type DepsUpgradeAdvisorDeps, + createDepsUpgradeAdvisorService, +} from './deps-upgrade-advisor.service'; +import { + DepsUpgradeAdvisorInputSchema, + DepsUpgradeAdvisorOutputSchema, +} from './schema'; + +const execFileAsync = promisify(execFile); + +export function registerDepsUpgradeAdvisorTool(server: McpServer) { + return registerDepsUpgradeAdvisorToolWithDeps( + server, + createDepsUpgradeAdvisorDeps(), + ); +} + +export function registerDepsUpgradeAdvisorToolWithDeps( + server: McpServer, + deps: DepsUpgradeAdvisorDeps, +) { + const service = createDepsUpgradeAdvisorService(deps); + + return server.registerTool( + 'deps_upgrade_advisor', + { + description: + 'Analyze outdated dependencies and return risk-bucketed upgrade recommendations', + inputSchema: DepsUpgradeAdvisorInputSchema, + outputSchema: DepsUpgradeAdvisorOutputSchema, + }, + async (input) => { + try { + const parsed = DepsUpgradeAdvisorInputSchema.parse(input); + const result = await service.advise(parsed); + + return { + structuredContent: result, + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `deps_upgrade_advisor failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); +} + +function createDepsUpgradeAdvisorDeps(): DepsUpgradeAdvisorDeps { + const rootPath = process.cwd(); + + return { + async executeCommand(command, args) { + try { + const result = await execFileAsync(command, args, { + cwd: rootPath, + maxBuffer: 1024 * 1024 * 10, + }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + } catch (error) { + if (isExecError(error)) { + return { + stdout: error.stdout ?? '', + stderr: error.stderr ?? '', + exitCode: error.code, + }; + } + + throw error; + } + }, + nowIso() { + return new Date().toISOString(); + }, + }; +} + +interface ExecError extends Error { + code: number; + stdout?: string; + stderr?: string; +} + +function isExecError(error: unknown): error is ExecError { + return error instanceof Error && 'code' in error; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +export { + createDepsUpgradeAdvisorService, + type DepsUpgradeAdvisorDeps, +} from './deps-upgrade-advisor.service'; +export type { DepsUpgradeAdvisorOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/deps-upgrade-advisor/schema.ts b/packages/mcp-server/src/tools/deps-upgrade-advisor/schema.ts new file mode 100644 index 000000000..3900cb7f7 --- /dev/null +++ b/packages/mcp-server/src/tools/deps-upgrade-advisor/schema.ts @@ -0,0 +1,52 @@ +import { z } from 'zod/v3'; + +export const DepsUpgradeAdvisorInputSchema = z.object({ + state: z + .object({ + includeMajor: z.boolean().optional(), + maxPackages: z.number().int().min(1).max(200).optional(), + includeDevDependencies: z.boolean().optional(), + }) + .optional(), +}); + +export const DepsUpgradeRecommendationSchema = z.object({ + package: z.string(), + workspace: z.string(), + dependency_type: z.string(), + current: z.string(), + wanted: z.string(), + latest: z.string(), + update_type: z.enum(['major', 'minor', 'patch', 'unknown']), + risk: z.enum(['high', 'medium', 'low']), + potentially_breaking: z.boolean(), + recommended_target: z.string(), + recommended_command: z.string(), + reason: z.string(), +}); + +export const DepsUpgradeAdvisorOutputSchema = z.object({ + generated_at: z.string(), + summary: z.object({ + total_outdated: z.number().int().min(0), + recommended_now: z.number().int().min(0), + major_available: z.number().int().min(0), + minor_or_patch_available: z.number().int().min(0), + }), + recommendations: z.array(DepsUpgradeRecommendationSchema), + grouped_commands: z.object({ + safe_batch_command: z.string().nullable(), + major_batch_command: z.string().nullable(), + }), + warnings: z.array(z.string()), +}); + +export type DepsUpgradeAdvisorInput = z.infer< + typeof DepsUpgradeAdvisorInputSchema +>; +export type DepsUpgradeRecommendation = z.infer< + typeof DepsUpgradeRecommendationSchema +>; +export type DepsUpgradeAdvisorOutput = z.infer< + typeof DepsUpgradeAdvisorOutputSchema +>; diff --git a/packages/mcp-server/src/tools/dev/__tests__/kit-dev.service.test.ts b/packages/mcp-server/src/tools/dev/__tests__/kit-dev.service.test.ts new file mode 100644 index 000000000..e49fbe860 --- /dev/null +++ b/packages/mcp-server/src/tools/dev/__tests__/kit-dev.service.test.ts @@ -0,0 +1,1016 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { + DEFAULT_PORT_CONFIG, + type KitDevServiceDeps, + createKitDevService, +} from '../kit-dev.service'; + +function createDeps( + overrides: Partial = {}, +): KitDevServiceDeps { + return { + rootPath: '/repo', + async resolveVariantContext() { + return { + variant: 'next-supabase', + variantFamily: 'supabase', + framework: 'nextjs', + }; + }, + async resolvePortConfig() { + return DEFAULT_PORT_CONFIG; + }, + async executeCommand(command: string, args: string[]) { + if ( + command === 'pnpm' && + args.join(' ') === '--filter web supabase status -o env' + ) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\nANON_KEY=test-anon\nSERVICE_ROLE_KEY=test-service\n', + stderr: '', + exitCode: 0, + }; + } + + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + }, + async spawnDetached() { + return { + pid: 999, + }; + }, + async isPortOpen() { + return false; + }, + async getPortProcess() { + return null; + }, + async isProcessRunning() { + return true; + }, + async findProcessesByName() { + return []; + }, + async killProcess() {}, + async sleep() {}, + async fetchJson() { + return { version: 'v1' }; + }, + ...overrides, + }; +} + +function createOrmDeps( + overrides: Partial = {}, +): KitDevServiceDeps { + return createDeps({ + async resolveVariantContext() { + return { + variant: 'next-drizzle', + variantFamily: 'orm', + framework: 'nextjs', + }; + }, + ...overrides, + }); +} + +describe('KitDevService', () => { + describe('start', () => { + it('starts all services in expected order and returns running statuses', async () => { + const executeCalls: string[] = []; + const spawnCalls: string[] = []; + + // Track which services have been started so ports become "open" + // after their respective start commands. + let supabaseStarted = false; + let appStarted = false; + + const deps = createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + executeCalls.push(full); + + if (full.includes('supabase:start')) { + supabaseStarted = true; + } + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\nANON_KEY=test-anon\nSERVICE_ROLE_KEY=test-service\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async spawnDetached(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + spawnCalls.push(full); + + if (full.includes('next dev')) { + appStarted = true; + return { pid: 111 }; + } + + return { pid: 222 }; + }, + async isPortOpen(port: number) { + // Ports become open only after their service is started + if ((port === 54321 || port === 54323) && supabaseStarted) { + return true; + } + + if (port === 3000 && appStarted) { + return true; + } + + return false; + }, + async isProcessRunning() { + return true; + }, + }); + + const service = createKitDevService(deps); + const result = await service.start({ services: ['all'] }); + + // Database should be started first (supabase:start before app/stripe spawns) + expect(executeCalls).toEqual( + expect.arrayContaining([expect.stringContaining('supabase:start')]), + ); + + const startIdx = executeCalls.findIndex((c) => + c.includes('supabase:start'), + ); + + expect(startIdx).toBeGreaterThanOrEqual(0); + expect(spawnCalls[0]).toContain('next dev'); + expect(spawnCalls[1]).toContain('stripe listen'); + + expect(result.services.find((s) => s.id === 'app')?.status).toBe( + 'running', + ); + expect(result.services.find((s) => s.id === 'database')?.status).toBe( + 'running', + ); + expect(result.services.find((s) => s.id === 'stripe')?.status).toBe( + 'running', + ); + expect(result.services.find((s) => s.id === 'mailbox')?.status).toBe( + 'running', + ); + }); + + it('skips already running services when detected on port', async () => { + const spawnCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async isPortOpen(port: number) { + // Port 3000 is already in use + return port === 3000; + }, + async getPortProcess(port: number) { + if (port === 3000) { + return { pid: 100, command: 'node' }; + } + + return null; + }, + async spawnDetached(command: string, args: string[]) { + spawnCalls.push(`${command} ${args.join(' ')}`); + return { pid: 200 }; + }, + }), + ); + + const result = await service.start({ services: ['app'] }); + + // Should not spawn a new process since the port is occupied + expect(spawnCalls).toHaveLength(0); + expect(result.services.find((s) => s.id === 'app')?.status).toBe( + 'running', + ); + }); + + it('skips already running stripe when process is detected', async () => { + const spawnCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async findProcessesByName() { + return [ + { + pid: 500, + command: + 'stripe listen --forward-to http://localhost:3000/api/billing/webhook', + }, + ]; + }, + async spawnDetached(command: string, args: string[]) { + spawnCalls.push(`${command} ${args.join(' ')}`); + return { pid: 600 }; + }, + }), + ); + + const result = await service.start({ services: ['stripe'] }); + + // Should not spawn a new process since stripe is already running + expect(spawnCalls).toHaveLength(0); + expect(result.services.find((s) => s.id === 'stripe')?.status).toBe( + 'running', + ); + }); + + it('starts only selected services when not "all"', async () => { + const spawnCalls: string[] = []; + const executeCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + executeCalls.push(full); + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\nANON_KEY=test-anon\nSERVICE_ROLE_KEY=test-service\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async spawnDetached(command: string, args: string[]) { + spawnCalls.push(`${command} ${args.join(' ')}`); + return { pid: 111 }; + }, + }), + ); + + const result = await service.start({ services: ['app'] }); + + // Should only start app — no supabase:start, no stripe + expect(executeCalls).not.toEqual( + expect.arrayContaining([expect.stringContaining('supabase:start')]), + ); + + expect(spawnCalls).toHaveLength(1); + expect(spawnCalls[0]).toContain('next dev'); + expect(result.services).toHaveLength(1); + expect(result.services[0]?.id).toBe('app'); + }); + + it('starts database with docker compose for ORM variants', async () => { + const executeCalls: string[] = []; + + const service = createKitDevService( + createOrmDeps({ + async executeCommand(command: string, args: string[]) { + executeCalls.push(`${command} ${args.join(' ')}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.start({ services: ['database'] }); + + expect(executeCalls).toEqual( + expect.arrayContaining([ + expect.stringContaining('docker compose up -d postgres'), + ]), + ); + + const db = result.services.find((s) => s.id === 'database'); + expect(db?.name).toBe('PostgreSQL'); + }); + + it('returns Supabase extras (studio_url, anon_key, service_role_key) for supabase variants', async () => { + let supabaseStarted = false; + + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase:start')) { + supabaseStarted = true; + } + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\nANON_KEY=test-anon\nSERVICE_ROLE_KEY=test-service\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async isPortOpen(port: number) { + if ((port === 54321 || port === 54323) && supabaseStarted) { + return true; + } + + return false; + }, + }), + ); + + const result = await service.start({ services: ['database'] }); + const db = result.services.find((s) => s.id === 'database'); + + expect(db?.extras).toBeDefined(); + expect(db?.extras?.studio_url).toBe('http://127.0.0.1:54323'); + expect(db?.extras?.anon_key).toBe('test-anon'); + expect(db?.extras?.service_role_key).toBe('test-service'); + }); + + it('reports app as running via PID when port is not yet open after start', async () => { + const service = createKitDevService( + createDeps({ + async spawnDetached() { + return { pid: 555 }; + }, + async isPortOpen() { + // Port never becomes open during this test + return false; + }, + async isProcessRunning(pid: number) { + // But the spawned process is alive + return pid === 555; + }, + }), + ); + + const result = await service.start({ services: ['app'] }); + const app = result.services.find((s) => s.id === 'app'); + + expect(app?.status).toBe('running'); + expect(app?.pid).toBe(555); + }); + + it('de-duplicates service ids when specified multiple times', async () => { + const spawnCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async spawnDetached(command: string, args: string[]) { + spawnCalls.push(`${command} ${args.join(' ')}`); + return { pid: 111 }; + }, + }), + ); + + const result = await service.start({ + services: ['app', 'app'], + }); + + // Only one app spawn, not two + const appSpawns = spawnCalls.filter((s) => s.includes('next dev')); + + expect(appSpawns).toHaveLength(1); + expect(result.services).toHaveLength(1); + }); + }); + + describe('stop', () => { + it('stops detected services by finding processes at runtime', async () => { + const killProcess = vi.fn(async () => {}); + + const service = createKitDevService( + createDeps({ + async getPortProcess(port: number) { + if (port === 3000) { + return { pid: 123, command: 'node' }; + } + + return null; + }, + async findProcessesByName() { + return [ + { + pid: 456, + command: + 'stripe listen --forward-to http://localhost:3000/api/billing/webhook', + }, + ]; + }, + killProcess, + }), + ); + + const result = await service.stop({ + services: ['app', 'stripe'], + }); + + expect(result.stopped).toEqual(expect.arrayContaining(['app', 'stripe'])); + expect(killProcess).toHaveBeenCalledWith(123, 'SIGTERM'); + expect(killProcess).toHaveBeenCalledWith(456, 'SIGTERM'); + }); + + it('stops all services when "all" is specified', async () => { + const killProcess = vi.fn(async () => {}); + const executeCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async getPortProcess(port: number) { + if (port === 3000) { + return { pid: 123, command: 'node' }; + } + + return null; + }, + async findProcessesByName() { + return [{ pid: 456, command: 'stripe listen' }]; + }, + killProcess, + async executeCommand(command: string, args: string[]) { + executeCalls.push(`${command} ${args.join(' ')}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.stop({ services: ['all'] }); + + expect(result.stopped).toEqual( + expect.arrayContaining(['app', 'database', 'mailbox', 'stripe']), + ); + }); + + it('stops supabase via supabase:stop for supabase variants', async () => { + const executeCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + executeCalls.push(`${command} ${args.join(' ')}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + await service.stop({ services: ['database'] }); + + expect(executeCalls).toEqual( + expect.arrayContaining([expect.stringContaining('supabase:stop')]), + ); + }); + + it('stops postgres via docker compose for ORM variants', async () => { + const executeCalls: string[] = []; + + const service = createKitDevService( + createOrmDeps({ + async executeCommand(command: string, args: string[]) { + executeCalls.push(`${command} ${args.join(' ')}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + await service.stop({ services: ['database'] }); + + expect(executeCalls).toEqual( + expect.arrayContaining([ + expect.stringContaining('docker compose stop postgres'), + ]), + ); + }); + + it('sends SIGKILL if SIGTERM does not kill the process', async () => { + let callCount = 0; + + const killProcess = vi.fn(async () => {}); + + const service = createKitDevService( + createDeps({ + async getPortProcess(port: number) { + if (port === 3000) { + return { pid: 123, command: 'node' }; + } + + return null; + }, + killProcess, + async isProcessRunning() { + callCount++; + // Still running after SIGTERM + return callCount <= 2; + }, + }), + ); + + await service.stop({ services: ['app'] }); + + expect(killProcess).toHaveBeenCalledWith(123, 'SIGTERM'); + expect(killProcess).toHaveBeenCalledWith(123, 'SIGKILL'); + }); + + it('succeeds even when no process is found on port', async () => { + const service = createKitDevService( + createDeps({ + async getPortProcess() { + return null; + }, + }), + ); + + const result = await service.stop({ services: ['app'] }); + expect(result.stopped).toContain('app'); + }); + }); + + describe('status', () => { + it('returns all services status', async () => { + const service = createKitDevService(createDeps()); + const result = await service.status(); + + expect(result.services).toHaveLength(4); + + const ids = result.services.map((s) => s.id); + expect(ids).toEqual(['app', 'database', 'mailbox', 'stripe']); + }); + + it('shows running status when port is open', async () => { + const service = createKitDevService( + createDeps({ + async isPortOpen(port: number) { + return port === 3000; + }, + }), + ); + + const result = await service.status(); + const app = result.services.find((s) => s.id === 'app'); + + expect(app?.status).toBe('running'); + expect(app?.url).toBe('http://localhost:3000'); + }); + + it('shows stopped status when port is closed', async () => { + const service = createKitDevService( + createDeps({ + async isPortOpen() { + return false; + }, + async isProcessRunning() { + return false; + }, + }), + ); + + const result = await service.status(); + const app = result.services.find((s) => s.id === 'app'); + + expect(app?.status).toBe('stopped'); + }); + + it('returns app PID from port process when running', async () => { + const service = createKitDevService( + createDeps({ + async isPortOpen(port: number) { + return port === 3000; + }, + async getPortProcess(port: number) { + if (port === 3000) { + return { pid: 42, command: 'node' }; + } + + return null; + }, + }), + ); + + const result = await service.status(); + const app = result.services.find((s) => s.id === 'app'); + + expect(app?.status).toBe('running'); + expect(app?.pid).toBe(42); + }); + + it('returns Supabase extras when database is running for supabase variant', async () => { + const service = createKitDevService( + createDeps({ + async isPortOpen(port: number) { + return port === 54321 || port === 54323; + }, + }), + ); + + const result = await service.status(); + const db = result.services.find((s) => s.id === 'database'); + + expect(db?.status).toBe('running'); + expect(db?.name).toBe('Supabase'); + expect(db?.extras?.anon_key).toBe('test-anon'); + expect(db?.extras?.service_role_key).toBe('test-service'); + expect(db?.extras?.studio_url).toBe('http://127.0.0.1:54323'); + }); + + it('returns PostgreSQL status for ORM variants', async () => { + const service = createKitDevService( + createOrmDeps({ + async isPortOpen(port: number) { + return port === 5432; + }, + }), + ); + + const result = await service.status(); + const db = result.services.find((s) => s.id === 'database'); + + expect(db?.status).toBe('running'); + expect(db?.name).toBe('PostgreSQL'); + expect(db?.port).toBe(5432); + expect(db?.url).toBe('postgresql://localhost:5432'); + }); + + it('returns stopped database for ORM variants when port is closed', async () => { + const service = createKitDevService( + createOrmDeps({ + async isPortOpen() { + return false; + }, + }), + ); + + const result = await service.status(); + const db = result.services.find((s) => s.id === 'database'); + + expect(db?.status).toBe('stopped'); + expect(db?.name).toBe('PostgreSQL'); + }); + + it('returns stripe as running when process is detected', async () => { + const service = createKitDevService( + createDeps({ + async findProcessesByName() { + return [ + { + pid: 777, + command: + 'stripe listen --forward-to http://localhost:3000/api/billing/webhook', + }, + ]; + }, + }), + ); + + const result = await service.status(); + const stripe = result.services.find((s) => s.id === 'stripe'); + + expect(stripe?.status).toBe('running'); + expect(stripe?.pid).toBe(777); + expect(stripe?.webhook_url).toBe( + 'http://localhost:3000/api/billing/webhook', + ); + }); + + it('extracts webhook_url from stripe process command line', async () => { + const service = createKitDevService( + createDeps({ + async findProcessesByName() { + return [ + { + pid: 777, + command: + 'stripe listen --forward-to http://localhost:4000/custom/webhook', + }, + ]; + }, + }), + ); + + const result = await service.status(); + const stripe = result.services.find((s) => s.id === 'stripe'); + + expect(stripe?.webhook_url).toBe('http://localhost:4000/custom/webhook'); + }); + + it('returns mailbox as running only when API is reachable', async () => { + const service = createKitDevService( + createDeps({ + async fetchJson(url: string) { + if (url.includes('/api/v1/info')) { + return { name: 'mailpit' }; + } + + return {}; + }, + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: 'mailpit\n', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.status(); + const mailbox = result.services.find((s) => s.id === 'mailbox'); + + expect(mailbox?.status).toBe('running'); + expect(mailbox?.name).toBe('Mailbox'); + expect(mailbox?.port).toBe(8025); + expect(mailbox?.url).toBe('http://localhost:8025'); + }); + + it('returns mailbox error when container is running but API is unreachable', async () => { + const service = createKitDevService( + createDeps({ + async fetchJson() { + throw new Error('connect ECONNREFUSED 127.0.0.1:54324'); + }, + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: 'mailpit\n', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.status(); + const mailbox = result.services.find((s) => s.id === 'mailbox'); + + expect(mailbox?.status).toBe('error'); + expect(mailbox?.extras?.reason).toContain('API is unreachable'); + expect(mailbox?.extras?.api_error).toContain('ECONNREFUSED'); + }); + }); + + describe('Supabase extras parsing', () => { + it('falls back to text output parsing when env output fails', async () => { + let callCount = 0; + + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase status -o env')) { + callCount++; + throw new Error('env output not supported'); + } + + if (full.includes('supabase:status')) { + callCount++; + + return { + stdout: [ + 'API URL: http://127.0.0.1:54321', + 'Studio URL: http://127.0.0.1:54323', + 'anon key: fallback-anon', + 'service_role key: fallback-sr', + ].join('\n'), + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async isPortOpen(port: number) { + return port === 54321 || port === 54323; + }, + }), + ); + + const result = await service.status(); + const db = result.services.find((s) => s.id === 'database'); + + expect(callCount).toBeGreaterThanOrEqual(2); + expect(db?.extras?.anon_key).toBe('fallback-anon'); + expect(db?.extras?.service_role_key).toBe('fallback-sr'); + }); + + it('returns empty extras when both supabase status methods fail', async () => { + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if ( + full.includes('supabase status') || + full.includes('supabase:status') + ) { + throw new Error('supabase not running'); + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async isPortOpen() { + return false; + }, + }), + ); + + const result = await service.status(); + const db = result.services.find((s) => s.id === 'database'); + + expect(db?.status).toBe('stopped'); + }); + }); + + describe('error propagation', () => { + it('propagates executeCommand error when supabase:start fails', async () => { + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase:start')) { + throw new Error('Docker is not running'); + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + await expect(service.start({ services: ['database'] })).rejects.toThrow( + 'Docker is not running', + ); + }); + + it('propagates executeCommand error when docker compose fails for ORM', async () => { + const service = createKitDevService( + createOrmDeps({ + async executeCommand(command: string, args: string[]) { + if (command === 'docker') { + throw new Error('Cannot connect to Docker daemon'); + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + await expect(service.start({ services: ['database'] })).rejects.toThrow( + 'Cannot connect to Docker daemon', + ); + }); + + it('propagates spawnDetached error when app fails to start', async () => { + const service = createKitDevService( + createDeps({ + async spawnDetached() { + throw new Error('Failed to spawn pnpm'); + }, + }), + ); + + await expect(service.start({ services: ['app'] })).rejects.toThrow( + 'Failed to spawn pnpm', + ); + }); + + it('stop succeeds even if database stop command throws', async () => { + const service = createKitDevService( + createDeps({ + async executeCommand() { + throw new Error('supabase CLI not found'); + }, + }), + ); + + // Should not throw — database stop errors are caught + const result = await service.stop({ services: ['database'] }); + expect(result.stopped).toContain('database'); + }); + }); + + describe('mailbox alias and dedicated status', () => { + it('handles mailpit alias in start and normalizes output id to mailbox', async () => { + let supabaseStarted = false; + const executeCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async fetchJson() { + if (supabaseStarted) { + return { ok: true }; + } + + throw new Error('connect ECONNREFUSED 127.0.0.1:54324'); + }, + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + executeCalls.push(full); + + if (full.includes('supabase:start')) { + supabaseStarted = true; + } + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + const startResult = await service.start({ services: ['mailpit'] }); + + expect(executeCalls).toEqual( + expect.arrayContaining([expect.stringContaining('supabase:start')]), + ); + expect(startResult.services).toHaveLength(1); + expect(startResult.services[0]?.id).toBe('mailbox'); + }); + + it('handles mailpit alias in stop and normalizes stopped id to mailbox', async () => { + const executeCalls: string[] = []; + + const service = createKitDevService( + createDeps({ + async executeCommand(command: string, args: string[]) { + executeCalls.push(`${command} ${args.join(' ')}`); + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.stop({ services: ['mailpit'] }); + + expect(executeCalls).toEqual( + expect.arrayContaining([expect.stringContaining('supabase:stop')]), + ); + expect(result.stopped).toContain('mailbox'); + expect(result.stopped).not.toContain('mailpit'); + }); + + it('exposes graceful mailbox status for UI fallback', async () => { + const service = createKitDevService( + createDeps({ + async fetchJson() { + throw new Error('connect ECONNREFUSED 127.0.0.1:54324'); + }, + async executeCommand(command: string, args: string[]) { + const full = `${command} ${args.join(' ')}`; + + if (full.includes('supabase status -o env')) { + return { + stdout: + 'API_URL=http://127.0.0.1:54321\nSTUDIO_URL=http://127.0.0.1:54323\n', + stderr: '', + exitCode: 0, + }; + } + + return { stdout: 'mailpit\n', stderr: '', exitCode: 0 }; + }, + }), + ); + + const mailboxStatus = await service.mailboxStatus(); + + expect(mailboxStatus).toEqual({ + connected: true, + running: false, + api_reachable: false, + port: 8025, + reason: 'Mailbox container is running, but Mailpit API is unreachable', + url: 'http://localhost:8025', + }); + }); + }); +}); diff --git a/packages/mcp-server/src/tools/dev/index.ts b/packages/mcp-server/src/tools/dev/index.ts new file mode 100644 index 000000000..c04006920 --- /dev/null +++ b/packages/mcp-server/src/tools/dev/index.ts @@ -0,0 +1,494 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile, spawn } from 'node:child_process'; +import { access, readFile } from 'node:fs/promises'; +import { Socket } from 'node:net'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + DEFAULT_PORT_CONFIG, + type KitDevServiceDeps, + createKitDevService, +} from './kit-dev.service'; +import { + KitDevStartInputSchema, + KitDevStartOutputSchema, + KitDevStatusInputSchema, + KitDevStatusOutputSchema, + KitDevStopInputSchema, + KitDevStopOutputSchema, + KitMailboxStatusInputSchema, + KitMailboxStatusOutputSchema, +} from './schema'; + +const execFileAsync = promisify(execFile); + +export function registerKitDevTools(server: McpServer) { + const service = createKitDevService(createKitDevDeps()); + + server.registerTool( + 'kit_dev_start', + { + description: 'Start all or selected local development services', + inputSchema: KitDevStartInputSchema, + outputSchema: KitDevStartOutputSchema, + }, + async (input) => { + const parsedInput = KitDevStartInputSchema.parse(input); + + try { + const result = await service.start(parsedInput); + + return { + structuredContent: result, + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_dev_start failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); + + server.registerTool( + 'kit_dev_stop', + { + description: 'Stop all or selected local development services', + inputSchema: KitDevStopInputSchema, + outputSchema: KitDevStopOutputSchema, + }, + async (input) => { + const parsedInput = KitDevStopInputSchema.parse(input); + + try { + const result = await service.stop(parsedInput); + + return { + structuredContent: result, + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_dev_stop failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); + + server.registerTool( + 'kit_dev_status', + { + description: + 'Check status for app, database, mailbox, and stripe local services', + inputSchema: KitDevStatusInputSchema, + outputSchema: KitDevStatusOutputSchema, + }, + async (input) => { + KitDevStatusInputSchema.parse(input); + + try { + const result = await service.status(); + + return { + structuredContent: result, + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_dev_status failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); + + server.registerTool( + 'kit_mailbox_status', + { + description: + 'Check local mailbox health with graceful fallback fields for UI state', + inputSchema: KitMailboxStatusInputSchema, + outputSchema: KitMailboxStatusOutputSchema, + }, + async (input) => { + KitMailboxStatusInputSchema.parse(input); + + try { + const result = await service.mailboxStatus(); + + return { + structuredContent: result, + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_mailbox_status failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); +} + +export function createKitDevDeps(rootPath = process.cwd()): KitDevServiceDeps { + return { + rootPath, + async resolveVariantContext() { + const packageJson = await readJsonIfPresent( + join(rootPath, 'apps', 'web', 'package.json'), + ); + const hasSupabase = await pathExists( + join(rootPath, 'apps', 'web', 'supabase'), + ); + + const dependencies = { + ...(packageJson?.dependencies ?? {}), + ...(packageJson?.devDependencies ?? {}), + } as Record; + + const framework = + 'react-router' in dependencies || '@react-router/dev' in dependencies + ? 'react-router' + : 'nextjs'; + + if (hasSupabase) { + return { + variant: + framework === 'react-router' + ? 'react-router-supabase' + : 'next-supabase', + variantFamily: 'supabase', + framework, + } as const; + } + + return { + variant: + framework === 'react-router' + ? 'react-router-drizzle' + : 'next-drizzle', + variantFamily: 'orm', + framework, + } as const; + }, + async resolvePortConfig() { + const configTomlPath = join( + rootPath, + 'apps', + 'web', + 'supabase', + 'config.toml', + ); + + let supabaseApiPort = DEFAULT_PORT_CONFIG.supabaseApiPort; + let supabaseStudioPort = DEFAULT_PORT_CONFIG.supabaseStudioPort; + + try { + const toml = await readFile(configTomlPath, 'utf8'); + supabaseApiPort = parseTomlSectionPort(toml, 'api') ?? supabaseApiPort; + supabaseStudioPort = + parseTomlSectionPort(toml, 'studio') ?? supabaseStudioPort; + } catch { + // config.toml not present or unreadable — use defaults. + } + + return { + appPort: DEFAULT_PORT_CONFIG.appPort, + supabaseApiPort, + supabaseStudioPort, + mailboxApiPort: DEFAULT_PORT_CONFIG.mailboxApiPort, + mailboxPort: DEFAULT_PORT_CONFIG.mailboxPort, + ormDbPort: DEFAULT_PORT_CONFIG.ormDbPort, + stripeWebhookPath: DEFAULT_PORT_CONFIG.stripeWebhookPath, + }; + }, + async executeCommand(command: string, args: string[]) { + const result = await executeWithFallback(rootPath, command, args); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + async spawnDetached(command: string, args: string[]) { + const child = spawn(command, args, { + cwd: rootPath, + detached: true, + stdio: 'ignore', + }); + + child.unref(); + + if (!child.pid) { + throw new Error(`Failed to spawn ${command}`); + } + + return { + pid: child.pid, + }; + }, + async isPortOpen(port: number) { + return checkPort(port); + }, + async fetchJson(url: string) { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP ${response.status} for ${url}`); + } + + return response.json(); + }, + async getPortProcess(port: number) { + try { + const result = await execFileAsync( + 'lsof', + ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc'], + { + cwd: rootPath, + }, + ); + + const pidLine = result.stdout + .split('\n') + .map((line) => line.trim()) + .find((line) => line.startsWith('p')); + + const commandLine = result.stdout + .split('\n') + .map((line) => line.trim()) + .find((line) => line.startsWith('c')); + + if (!pidLine || !commandLine) { + return null; + } + + const pid = Number(pidLine.slice(1)); + + if (!Number.isFinite(pid)) { + return null; + } + + return { + pid, + command: commandLine.slice(1), + }; + } catch { + return null; + } + }, + async isProcessRunning(pid: number) { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + }, + async findProcessesByName(pattern: string) { + try { + const result = await execFileAsync('pgrep', ['-fl', pattern], { + cwd: rootPath, + }); + + return result.stdout + .split('\n') + .filter(Boolean) + .map((line) => { + const spaceIdx = line.indexOf(' '); + + if (spaceIdx <= 0) { + return null; + } + + const pid = Number(line.slice(0, spaceIdx)); + const command = line.slice(spaceIdx + 1).trim(); + + if (!Number.isFinite(pid)) { + return null; + } + + return { pid, command }; + }) + .filter((p): p is { pid: number; command: string } => p !== null); + } catch { + return []; + } + }, + async killProcess(pid: number, signal = 'SIGTERM') { + try { + // Kill the entire process group (negative PID) since services + // are spawned detached and become process group leaders. + process.kill(-pid, signal); + } catch { + // Fall back to killing the individual process if group kill fails. + process.kill(pid, signal); + } + }, + async sleep(ms: number) { + await new Promise((resolve) => setTimeout(resolve, ms)); + }, + }; +} + +async function executeWithFallback( + rootPath: string, + command: string, + args: string[], +) { + try { + return await execFileAsync(command, args, { + cwd: rootPath, + }); + } catch (error) { + if (isLocalCliCandidate(command)) { + const localBinCandidates = [ + join(rootPath, 'node_modules', '.bin', command), + join(rootPath, 'apps', 'web', 'node_modules', '.bin', command), + ]; + + for (const localBin of localBinCandidates) { + try { + return await execFileAsync(localBin, args, { + cwd: rootPath, + }); + } catch { + // Try next local binary candidate. + } + } + + try { + return await execFileAsync('pnpm', ['exec', command, ...args], { + cwd: rootPath, + }); + } catch { + return execFileAsync( + 'pnpm', + ['--filter', 'web', 'exec', command, ...args], + { + cwd: rootPath, + }, + ); + } + } + + throw error; + } +} + +function isLocalCliCandidate(command: string) { + return command === 'supabase' || command === 'stripe'; +} + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function readJsonIfPresent(path: string) { + try { + const content = await readFile(path, 'utf8'); + return JSON.parse(content) as { + dependencies?: Record; + devDependencies?: Record; + }; + } catch { + return null; + } +} + +async function checkPort(port: number) { + return new Promise((resolve) => { + const socket = new Socket(); + + socket.setTimeout(200); + + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.once('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.once('error', () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, '127.0.0.1'); + }); +} + +function parseTomlSectionPort( + content: string, + section: string, +): number | undefined { + const lines = content.split('\n'); + let inSection = false; + + for (const line of lines) { + const trimmed = line.trim(); + + if (trimmed.startsWith('[')) { + inSection = trimmed === `[${section}]`; + continue; + } + + if (inSection) { + const match = trimmed.match(/^port\s*=\s*(\d+)/); + + if (match) { + return Number(match[1]); + } + } + } + + return undefined; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +export { createKitDevService, DEFAULT_PORT_CONFIG } from './kit-dev.service'; +export type { KitDevServiceDeps, PortConfig } from './kit-dev.service'; +export type { + KitDevStartOutput, + KitDevStatusOutput, + KitDevStopOutput, +} from './schema'; diff --git a/packages/mcp-server/src/tools/dev/kit-dev.service.ts b/packages/mcp-server/src/tools/dev/kit-dev.service.ts new file mode 100644 index 000000000..30eaba215 --- /dev/null +++ b/packages/mcp-server/src/tools/dev/kit-dev.service.ts @@ -0,0 +1,723 @@ +import type { + DevServiceId, + DevServiceSelection, + DevServiceStatusItem, + KitDevStartInput, + KitDevStartOutput, + KitDevStatusOutput, + KitDevStopInput, + KitDevStopOutput, + KitMailboxStatusOutput, +} from './schema'; + +type VariantFamily = 'supabase' | 'orm'; +type Framework = 'nextjs' | 'react-router'; + +type SignalName = 'SIGTERM' | 'SIGKILL'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface ProcessInfo { + pid: number; + command: string; +} + +interface SpawnedProcess { + pid: number; +} + +interface VariantContext { + variant: string; + variantFamily: VariantFamily; + framework: Framework; +} + +export interface PortConfig { + appPort: number; + supabaseApiPort: number; + supabaseStudioPort: number; + mailboxApiPort: number; + mailboxPort: number; + ormDbPort: number; + stripeWebhookPath: string; +} + +export const DEFAULT_PORT_CONFIG: PortConfig = { + appPort: 3000, + supabaseApiPort: 54321, + supabaseStudioPort: 54323, + mailboxApiPort: 54324, + mailboxPort: 8025, + ormDbPort: 5432, + stripeWebhookPath: '/api/billing/webhook', +}; + +export interface KitDevServiceDeps { + rootPath: string; + resolveVariantContext(): Promise; + resolvePortConfig(): Promise; + executeCommand(command: string, args: string[]): Promise; + spawnDetached(command: string, args: string[]): Promise; + isPortOpen(port: number): Promise; + getPortProcess(port: number): Promise; + isProcessRunning(pid: number): Promise; + findProcessesByName(pattern: string): Promise; + killProcess(pid: number, signal?: SignalName): Promise; + sleep(ms: number): Promise; + fetchJson(url: string): Promise; +} + +interface MailboxHealth { + connected: boolean; + running: boolean; + apiReachable: boolean; + url: string; + port: number; + reason?: string; + diagnostics?: Record; +} + +export function createKitDevService(deps: KitDevServiceDeps) { + return new KitDevService(deps); +} + +export class KitDevService { + constructor(private readonly deps: KitDevServiceDeps) {} + + async start(input: KitDevStartInput): Promise { + const selectedServices = this.expandServices(input.services); + const variant = await this.deps.resolveVariantContext(); + const ports = await this.deps.resolvePortConfig(); + + const startedPids: Partial> = {}; + + if (selectedServices.includes('database')) { + const running = await this.isDatabaseRunning(variant, ports); + + if (!running) { + await this.startDatabase(variant); + } + } + + if ( + selectedServices.includes('mailbox') && + variant.variantFamily === 'supabase' + ) { + const mailbox = await this.collectMailboxHealth(variant, ports); + + if (!mailbox.connected) { + await this.startDatabase(variant); + } + } + + if (selectedServices.includes('app')) { + const running = await this.deps.isPortOpen(ports.appPort); + + if (!running) { + startedPids.app = await this.startApp(variant, ports); + } + } + + if (selectedServices.includes('stripe')) { + const procs = await this.deps.findProcessesByName('stripe.*listen'); + + if (procs.length === 0) { + startedPids.stripe = await this.startStripe(ports); + } + } + + const status = await this.collectStatus(variant, ports, startedPids); + + return { + services: status.filter((service) => + selectedServices.includes(service.id), + ), + }; + } + + async stop(input: KitDevStopInput): Promise { + const selectedServices = this.expandServices(input.services); + const variant = await this.deps.resolveVariantContext(); + const ports = await this.deps.resolvePortConfig(); + + const stopped = new Set(); + + if (selectedServices.includes('stripe')) { + const procs = await this.deps.findProcessesByName('stripe.*listen'); + + for (const proc of procs) { + await this.stopProcess(proc.pid); + } + + stopped.add('stripe'); + } + + if (selectedServices.includes('app')) { + const proc = await this.deps.getPortProcess(ports.appPort); + + if (proc) { + await this.stopProcess(proc.pid); + } + + stopped.add('app'); + } + + const shouldStopDatabase = + selectedServices.includes('database') || + (selectedServices.includes('mailbox') && + variant.variantFamily === 'supabase'); + + if (shouldStopDatabase) { + try { + await this.stopDatabase(variant); + } catch { + // Best-effort — the database process may already be stopped or + // the CLI may not be available. + } + + if (selectedServices.includes('database')) { + stopped.add('database'); + } + if (selectedServices.includes('mailbox')) { + stopped.add('mailbox'); + } + } else if (selectedServices.includes('mailbox')) { + stopped.add('mailbox'); + } + + return { + stopped: Array.from(stopped), + }; + } + + async status(): Promise { + const variant = await this.deps.resolveVariantContext(); + const ports = await this.deps.resolvePortConfig(); + + const services = await this.collectStatus(variant, ports); + + return { + services, + }; + } + + async mailboxStatus(): Promise { + const variant = await this.deps.resolveVariantContext(); + const ports = await this.deps.resolvePortConfig(); + const mailbox = await this.collectMailboxHealth(variant, ports); + + return { + connected: mailbox.connected, + running: mailbox.running, + api_reachable: mailbox.apiReachable, + url: mailbox.url, + port: mailbox.port, + reason: mailbox.reason, + }; + } + + private expandServices(services: DevServiceSelection[]): DevServiceId[] { + if (services.includes('all')) { + return ['app', 'database', 'mailbox', 'stripe']; + } + + const normalized = services.map((service) => + service === 'mailpit' ? 'mailbox' : service, + ); + + return Array.from(new Set(normalized)) as DevServiceId[]; + } + + private async isDatabaseRunning( + variant: VariantContext, + ports: PortConfig, + ): Promise { + if (variant.variantFamily === 'supabase') { + const apiOpen = await this.deps.isPortOpen(ports.supabaseApiPort); + const studioOpen = await this.deps.isPortOpen(ports.supabaseStudioPort); + + return apiOpen || studioOpen; + } + + return this.deps.isPortOpen(ports.ormDbPort); + } + + private async startDatabase(variant: VariantContext) { + if (variant.variantFamily === 'supabase') { + await this.deps.executeCommand('pnpm', [ + '--filter', + 'web', + 'supabase:start', + ]); + await this.deps.sleep(400); + + return; + } + + await this.deps.executeCommand('docker', [ + 'compose', + 'up', + '-d', + 'postgres', + ]); + } + + private async stopDatabase(variant: VariantContext) { + if (variant.variantFamily === 'supabase') { + await this.deps.executeCommand('pnpm', [ + '--filter', + 'web', + 'supabase:stop', + ]); + return; + } + + await this.deps.executeCommand('docker', ['compose', 'stop', 'postgres']); + } + + private async startApp( + variant: VariantContext, + ports: PortConfig, + ): Promise { + const args = + variant.framework === 'react-router' + ? ['exec', 'react-router', 'dev', '--port', String(ports.appPort)] + : [ + '--filter', + 'web', + 'exec', + 'next', + 'dev', + '--port', + String(ports.appPort), + ]; + + const process = await this.deps.spawnDetached('pnpm', args); + + await this.deps.sleep(500); + + return process.pid; + } + + private async startStripe(ports: PortConfig): Promise { + const webhookUrl = `http://localhost:${ports.appPort}${ports.stripeWebhookPath}`; + const process = await this.deps.spawnDetached('pnpm', [ + 'exec', + 'stripe', + 'listen', + '--forward-to', + webhookUrl, + ]); + + return process.pid; + } + + private async collectStatus( + variant: VariantContext, + ports: PortConfig, + startedPids: Partial> = {}, + ): Promise { + const app = await this.collectAppStatus(variant, ports, startedPids); + const database = await this.collectDatabaseStatus(variant, ports); + const mailbox = await this.collectMailboxStatus(variant, ports); + const stripe = await this.collectStripeStatus(ports, startedPids); + + return [app, database, mailbox, stripe]; + } + + private async collectAppStatus( + variant: VariantContext, + ports: PortConfig, + startedPids: Partial> = {}, + ): Promise { + const name = + variant.framework === 'react-router' + ? 'React Router Dev Server' + : 'Next.js Dev Server'; + + const portOpen = await this.deps.isPortOpen(ports.appPort); + const proc = portOpen + ? await this.deps.getPortProcess(ports.appPort) + : null; + + // If we just started the app, the port may not be open yet. + // Fall back to checking if the spawned process is alive. + const justStartedPid = startedPids.app; + const justStartedAlive = justStartedPid + ? await this.deps.isProcessRunning(justStartedPid) + : false; + + const running = portOpen || justStartedAlive; + + return { + id: 'app', + name, + status: running ? 'running' : 'stopped', + port: ports.appPort, + url: running ? `http://localhost:${ports.appPort}` : undefined, + pid: proc?.pid ?? (justStartedAlive ? justStartedPid : null) ?? null, + }; + } + + private async collectDatabaseStatus( + variant: VariantContext, + ports: PortConfig, + ): Promise { + if (variant.variantFamily === 'supabase') { + const extras = await this.resolveSupabaseExtras(); + const apiPort = + extractPortFromUrl(extras.api_url) ?? ports.supabaseApiPort; + const studioPort = + extractPortFromUrl(extras.studio_url) ?? ports.supabaseStudioPort; + const portOpen = await this.deps.isPortOpen(apiPort); + const studioOpen = await this.deps.isPortOpen(studioPort); + const running = portOpen || studioOpen; + + return { + id: 'database', + name: 'Supabase', + status: running ? 'running' : 'stopped', + port: apiPort, + url: + extras.api_url ?? + (running ? `http://127.0.0.1:${apiPort}` : undefined), + extras: running + ? { + ...(extras.studio_url ? { studio_url: extras.studio_url } : {}), + ...(extras.anon_key ? { anon_key: extras.anon_key } : {}), + ...(extras.service_role_key + ? { service_role_key: extras.service_role_key } + : {}), + } + : undefined, + }; + } + + const running = await this.deps.isPortOpen(ports.ormDbPort); + + return { + id: 'database', + name: 'PostgreSQL', + status: running ? 'running' : 'stopped', + port: ports.ormDbPort, + url: running ? `postgresql://localhost:${ports.ormDbPort}` : undefined, + }; + } + + private async collectStripeStatus( + ports: PortConfig, + startedPids: Partial> = {}, + ): Promise { + const procs = await this.deps.findProcessesByName('stripe.*listen'); + const justStartedPid = startedPids.stripe; + const justStartedAlive = justStartedPid + ? await this.deps.isProcessRunning(justStartedPid) + : false; + + const running = procs.length > 0 || justStartedAlive; + const pid = + procs[0]?.pid ?? (justStartedAlive ? justStartedPid : undefined); + + const webhookUrl = procs[0]?.command + ? (extractForwardToUrl(procs[0].command) ?? + `http://localhost:${ports.appPort}${ports.stripeWebhookPath}`) + : `http://localhost:${ports.appPort}${ports.stripeWebhookPath}`; + + return { + id: 'stripe', + name: 'Stripe CLI', + status: running ? 'running' : 'stopped', + pid: pid ?? null, + webhook_url: running ? webhookUrl : undefined, + }; + } + + private async collectMailboxStatus( + variant: VariantContext, + ports: PortConfig, + ): Promise { + const mailbox = await this.collectMailboxHealth(variant, ports); + + return { + id: 'mailbox', + name: 'Mailbox', + status: mailbox.running + ? 'running' + : mailbox.connected && !mailbox.apiReachable + ? 'error' + : 'stopped', + port: mailbox.port, + url: mailbox.url, + extras: mailbox.diagnostics, + }; + } + + private async collectMailboxHealth( + variant: VariantContext, + ports: PortConfig, + ): Promise { + const mailboxUrl = `http://localhost:${ports.mailboxPort}`; + const mailboxApiUrl = `http://127.0.0.1:${ports.mailboxApiPort}/api/v1/info`; + + if (variant.variantFamily !== 'supabase') { + return { + connected: false, + running: false, + apiReachable: false, + url: mailboxUrl, + port: ports.mailboxPort, + reason: 'Mailbox is only available for Supabase variants', + }; + } + + const [apiReachable, containerStatus] = await Promise.all([ + this.checkMailboxApi(mailboxApiUrl), + this.resolveMailboxContainerStatus(), + ]); + + if (apiReachable.ok) { + return { + connected: true, + running: true, + apiReachable: true, + url: mailboxUrl, + port: ports.mailboxPort, + }; + } + + if (containerStatus.running) { + const reason = + 'Mailbox container is running, but Mailpit API is unreachable'; + + return { + connected: true, + running: false, + apiReachable: false, + url: mailboxUrl, + port: ports.mailboxPort, + reason, + diagnostics: { + reason, + api_url: mailboxApiUrl, + ...(apiReachable.error ? { api_error: apiReachable.error } : {}), + ...(containerStatus.source + ? { container_source: containerStatus.source } + : {}), + ...(containerStatus.details + ? { container_details: containerStatus.details } + : {}), + }, + }; + } + + return { + connected: false, + running: false, + apiReachable: false, + url: mailboxUrl, + port: ports.mailboxPort, + reason: 'Mailbox is not running', + diagnostics: { + reason: 'Mailbox is not running', + api_url: mailboxApiUrl, + ...(apiReachable.error ? { api_error: apiReachable.error } : {}), + ...(containerStatus.details + ? { container_details: containerStatus.details } + : {}), + }, + }; + } + + private async resolveMailboxContainerStatus(): Promise<{ + running: boolean; + source?: string; + details?: string; + }> { + const dockerComposeResult = await this.tryGetRunningServicesFromCommand( + 'docker', + [ + 'compose', + '-f', + 'docker-compose.dev.yml', + 'ps', + '--status', + 'running', + '--services', + ], + 'docker-compose.dev.yml', + ); + + if (dockerComposeResult.running) { + return dockerComposeResult; + } + + const supabaseDockerResult = await this.tryGetRunningServicesFromCommand( + 'docker', + ['ps', '--format', '{{.Names}}'], + 'docker ps', + ); + + return supabaseDockerResult; + } + + private async tryGetRunningServicesFromCommand( + command: string, + args: string[], + source: string, + ): Promise<{ running: boolean; source?: string; details?: string }> { + try { + const result = await this.deps.executeCommand(command, args); + const serviceLines = result.stdout + .split('\n') + .map((line) => line.trim().toLowerCase()) + .filter(Boolean); + const running = serviceLines.some((line) => + /(mailpit|inbucket)/.test(line), + ); + + return { + running, + source, + }; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + + return { + running: false, + source, + details: message, + }; + } + } + + private async checkMailboxApi(url: string): Promise<{ + ok: boolean; + error?: string; + }> { + try { + await this.deps.fetchJson(url); + + return { ok: true }; + } catch (error) { + return { + ok: false, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } + } + + private async resolveSupabaseExtras() { + try { + const result = await this.deps.executeCommand('pnpm', [ + '--filter', + 'web', + 'supabase', + 'status', + '-o', + 'env', + ]); + + return parseSupabaseEnvOutput(result.stdout); + } catch { + try { + const result = await this.deps.executeCommand('pnpm', [ + '--filter', + 'web', + 'supabase:status', + ]); + + return parseSupabaseTextOutput(result.stdout); + } catch { + return { + api_url: undefined, + studio_url: undefined, + anon_key: undefined, + service_role_key: undefined, + }; + } + } + } + + private async stopProcess(pid: number) { + try { + await this.deps.killProcess(pid, 'SIGTERM'); + await this.deps.sleep(200); + const running = await this.deps.isProcessRunning(pid); + + if (running) { + await this.deps.killProcess(pid, 'SIGKILL'); + } + } catch { + // noop - process may already be dead. + } + } +} + +function parseSupabaseEnvOutput(output: string) { + const values: Record = {}; + + for (const line of output.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) { + continue; + } + + const idx = trimmed.indexOf('='); + if (idx <= 0) { + continue; + } + + const key = trimmed.slice(0, idx).trim(); + const value = trimmed.slice(idx + 1).trim(); + + if (key) { + values[key] = value; + } + } + + return { + api_url: values.API_URL, + studio_url: values.STUDIO_URL, + anon_key: values.ANON_KEY, + service_role_key: values.SERVICE_ROLE_KEY, + }; +} + +function extractPortFromUrl(url: string | undefined): number | undefined { + if (!url) { + return undefined; + } + + try { + const parsed = new URL(url); + const port = Number(parsed.port); + + return Number.isFinite(port) && port > 0 ? port : undefined; + } catch { + return undefined; + } +} + +function parseSupabaseTextOutput(output: string) { + const findValue = (label: string) => { + const regex = new RegExp(`${label}\\s*:\\s*(.+)`); + const match = output.match(regex); + return match?.[1]?.trim(); + }; + + return { + api_url: findValue('API URL'), + studio_url: findValue('Studio URL'), + anon_key: findValue('anon key'), + service_role_key: findValue('service_role key'), + }; +} + +function extractForwardToUrl(command: string): string | undefined { + const match = command.match(/--forward-to\s+(\S+)/); + return match?.[1]; +} diff --git a/packages/mcp-server/src/tools/dev/schema.ts b/packages/mcp-server/src/tools/dev/schema.ts new file mode 100644 index 000000000..88066526b --- /dev/null +++ b/packages/mcp-server/src/tools/dev/schema.ts @@ -0,0 +1,69 @@ +import { z } from 'zod/v3'; + +const DevServiceIdSchema = z.enum(['app', 'database', 'stripe', 'mailbox']); +const DevServiceSelectionSchema = z.enum([ + 'all', + 'app', + 'database', + 'stripe', + 'mailbox', + 'mailpit', +]); + +const DevServiceStatusItemSchema = z.object({ + id: DevServiceIdSchema, + name: z.string(), + status: z.enum(['running', 'stopped', 'error']), + port: z.number().nullable().optional(), + url: z.string().optional(), + pid: z.number().nullable().optional(), + webhook_url: z.string().optional(), + extras: z.record(z.string()).optional(), +}); + +export const KitDevStartInputSchema = z.object({ + services: z.array(DevServiceSelectionSchema).min(1).default(['all']), +}); + +export const KitDevStartOutputSchema = z.object({ + services: z.array(DevServiceStatusItemSchema), +}); + +export const KitDevStopInputSchema = z.object({ + services: z.array(DevServiceSelectionSchema).min(1).default(['all']), +}); + +export const KitDevStopOutputSchema = z.object({ + stopped: z.array(DevServiceIdSchema), +}); + +export const KitDevStatusInputSchema = z.object({}); + +export const KitDevStatusOutputSchema = z.object({ + services: z.array(DevServiceStatusItemSchema), +}); + +export const KitMailboxStatusInputSchema = z.object({}); + +export const KitMailboxStatusOutputSchema = z.object({ + connected: z.boolean(), + running: z.boolean(), + api_reachable: z.boolean(), + url: z.string().optional(), + port: z.number().optional(), + reason: z.string().optional(), +}); + +export type DevServiceId = z.infer; +export type DevServiceSelection = z.infer; +export type DevServiceStatusItem = z.infer; +export type KitDevStartInput = z.infer; +export type KitDevStartOutput = z.infer; +export type KitDevStopInput = z.infer; +export type KitDevStopOutput = z.infer; +export type KitDevStatusInput = z.infer; +export type KitDevStatusOutput = z.infer; +export type KitMailboxStatusInput = z.infer; +export type KitMailboxStatusOutput = z.infer< + typeof KitMailboxStatusOutputSchema +>; diff --git a/packages/mcp-server/src/tools/emails/__tests__/kit-emails.service.test.ts b/packages/mcp-server/src/tools/emails/__tests__/kit-emails.service.test.ts new file mode 100644 index 000000000..43277309a --- /dev/null +++ b/packages/mcp-server/src/tools/emails/__tests__/kit-emails.service.test.ts @@ -0,0 +1,292 @@ +import { describe, expect, it } from 'vitest'; + +import { + type KitEmailsDeps, + createKitEmailsService, +} from '../kit-emails.service'; + +function createDeps( + files: Record, + directories: string[], +): KitEmailsDeps { + const store = { ...files }; + const dirSet = new Set(directories); + + return { + rootPath: '/repo', + async readFile(filePath: string) { + if (!(filePath in store)) { + const error = new Error( + `ENOENT: no such file: ${filePath}`, + ) as NodeJS.ErrnoException; + + error.code = 'ENOENT'; + throw error; + } + + return store[filePath]!; + }, + async readdir(dirPath: string) { + if (!dirSet.has(dirPath)) { + return []; + } + + return Object.keys(store) + .filter((p) => { + const parent = p.substring(0, p.lastIndexOf('/')); + return parent === dirPath; + }) + .map((p) => p.substring(p.lastIndexOf('/') + 1)); + }, + async fileExists(filePath: string) { + return filePath in store || dirSet.has(filePath); + }, + async renderReactEmail() { + return null; + }, + }; +} + +const REACT_DIR = '/repo/packages/email-templates/src/emails'; +const SUPABASE_DIR = '/repo/apps/web/supabase/templates'; + +describe('KitEmailsService.list', () => { + it('discovers React Email templates with -email suffix in id', async () => { + const deps = createDeps( + { + [`${REACT_DIR}/invite.email.tsx`]: + 'export function renderInviteEmail() {}', + [`${REACT_DIR}/otp.email.tsx`]: 'export function renderOtpEmail() {}', + }, + [REACT_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + expect(result.templates).toHaveLength(2); + expect(result.categories).toEqual(['transactional']); + expect(result.total).toBe(2); + + const invite = result.templates.find((t) => t.id === 'invite-email'); + + expect(invite).toBeDefined(); + expect(invite!.name).toBe('Invite'); + expect(invite!.category).toBe('transactional'); + expect(invite!.file).toBe( + 'packages/email-templates/src/emails/invite.email.tsx', + ); + + const otp = result.templates.find((t) => t.id === 'otp-email'); + + expect(otp).toBeDefined(); + expect(otp!.name).toBe('Otp'); + }); + + it('discovers Supabase Auth HTML templates', async () => { + const deps = createDeps( + { + [`${SUPABASE_DIR}/magic-link.html`]: 'magic', + [`${SUPABASE_DIR}/reset-password.html`]: 'reset', + }, + [SUPABASE_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + expect(result.templates).toHaveLength(2); + expect(result.categories).toEqual(['supabase-auth']); + + const magicLink = result.templates.find((t) => t.id === 'magic-link'); + + expect(magicLink).toBeDefined(); + expect(magicLink!.name).toBe('Magic Link'); + expect(magicLink!.category).toBe('supabase-auth'); + expect(magicLink!.file).toBe('apps/web/supabase/templates/magic-link.html'); + }); + + it('discovers both types and returns sorted categories', async () => { + const deps = createDeps( + { + [`${REACT_DIR}/invite.email.tsx`]: + 'export function renderInviteEmail() {}', + [`${SUPABASE_DIR}/confirm-email.html`]: 'confirm', + }, + [REACT_DIR, SUPABASE_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + expect(result.templates).toHaveLength(2); + expect(result.categories).toEqual(['supabase-auth', 'transactional']); + expect(result.total).toBe(2); + }); + + it('handles empty directories gracefully', async () => { + const deps = createDeps({}, []); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + expect(result.templates).toEqual([]); + expect(result.categories).toEqual([]); + expect(result.total).toBe(0); + }); + + it('ignores non-email files in the directories', async () => { + const deps = createDeps( + { + [`${REACT_DIR}/invite.email.tsx`]: + 'export function renderInviteEmail() {}', + [`${REACT_DIR}/utils.ts`]: 'export const helper = true;', + [`${REACT_DIR}/README.md`]: '# readme', + [`${SUPABASE_DIR}/magic-link.html`]: 'magic', + [`${SUPABASE_DIR}/config.json`]: '{}', + }, + [REACT_DIR, SUPABASE_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + expect(result.templates).toHaveLength(2); + }); + + it('avoids id collision between React otp-email and Supabase otp', async () => { + const deps = createDeps( + { + [`${REACT_DIR}/otp.email.tsx`]: 'export function renderOtpEmail() {}', + [`${SUPABASE_DIR}/otp.html`]: 'otp', + }, + [REACT_DIR, SUPABASE_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.list(); + + const ids = result.templates.map((t) => t.id); + + expect(ids).toContain('otp-email'); + expect(ids).toContain('otp'); + expect(new Set(ids).size).toBe(ids.length); + }); +}); + +describe('KitEmailsService.read', () => { + it('reads a React Email template and extracts props', async () => { + const source = ` +interface Props { + teamName: string; + teamLogo?: string; + inviter: string | undefined; + invitedUserEmail: string; + link: string; + productName: string; + language?: string; +} + +export async function renderInviteEmail(props: Props) {} +`; + + const deps = createDeps( + { + [`${REACT_DIR}/invite.email.tsx`]: source, + }, + [REACT_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.read({ id: 'invite-email' }); + + expect(result.id).toBe('invite-email'); + expect(result.name).toBe('Invite'); + expect(result.category).toBe('transactional'); + expect(result.source).toBe(source); + + expect(result.props).toEqual([ + { name: 'teamName', type: 'string', required: true }, + { name: 'teamLogo', type: 'string', required: false }, + { name: 'inviter', type: 'string | undefined', required: true }, + { name: 'invitedUserEmail', type: 'string', required: true }, + { name: 'link', type: 'string', required: true }, + { name: 'productName', type: 'string', required: true }, + { name: 'language', type: 'string', required: false }, + ]); + }); + + it('reads a Supabase HTML template with empty props', async () => { + const html = 'Magic Link'; + + const deps = createDeps( + { + [`${SUPABASE_DIR}/magic-link.html`]: html, + }, + [SUPABASE_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.read({ id: 'magic-link' }); + + expect(result.id).toBe('magic-link'); + expect(result.source).toBe(html); + expect(result.props).toEqual([]); + }); + + it('throws for unknown template id', async () => { + const deps = createDeps({}, []); + + const service = createKitEmailsService(deps); + + await expect(service.read({ id: 'nonexistent' })).rejects.toThrow( + 'Email template not found: "nonexistent"', + ); + }); + + it('handles templates without Props interface', async () => { + const source = + 'export async function renderSimpleEmail() { return { html: "" }; }'; + + const deps = createDeps( + { + [`${REACT_DIR}/simple.email.tsx`]: source, + }, + [REACT_DIR], + ); + + const service = createKitEmailsService(deps); + const result = await service.read({ id: 'simple-email' }); + + expect(result.props).toEqual([]); + }); +}); + +describe('Path safety', () => { + it('rejects ids with path traversal', async () => { + const deps = createDeps({}, []); + const service = createKitEmailsService(deps); + + await expect(service.read({ id: '../etc/passwd' })).rejects.toThrow( + 'Template id must not contain ".."', + ); + }); + + it('rejects ids with forward slashes', async () => { + const deps = createDeps({}, []); + const service = createKitEmailsService(deps); + + await expect(service.read({ id: 'foo/bar' })).rejects.toThrow( + 'Template id must not include path separators', + ); + }); + + it('rejects ids with backslashes', async () => { + const deps = createDeps({}, []); + const service = createKitEmailsService(deps); + + await expect(service.read({ id: 'foo\\bar' })).rejects.toThrow( + 'Template id must not include path separators', + ); + }); +}); diff --git a/packages/mcp-server/src/tools/emails/index.ts b/packages/mcp-server/src/tools/emails/index.ts new file mode 100644 index 000000000..9ae041a90 --- /dev/null +++ b/packages/mcp-server/src/tools/emails/index.ts @@ -0,0 +1,109 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; + +import { + type KitEmailsDeps, + createKitEmailsDeps, + createKitEmailsService, +} from './kit-emails.service'; +import { + KitEmailsListInputSchema, + KitEmailsListOutputSchema, + KitEmailsReadInputSchema, + KitEmailsReadOutputSchema, +} from './schema'; + +type TextContent = { + type: 'text'; + text: string; +}; + +export function registerKitEmailTemplatesTools(server: McpServer) { + const service = createKitEmailsService(createKitEmailsDeps()); + + server.registerTool( + 'kit_email_templates_list', + { + description: + 'List project email template files (React Email + Supabase auth templates), not received inbox messages', + inputSchema: KitEmailsListInputSchema, + outputSchema: KitEmailsListOutputSchema, + }, + async () => { + try { + const result = await service.list(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_email_templates_list', error); + } + }, + ); + + server.registerTool( + 'kit_email_templates_read', + { + description: + 'Read a project email template source file by template id, with extracted props and optional rendered HTML sample', + inputSchema: KitEmailsReadInputSchema, + outputSchema: KitEmailsReadOutputSchema, + }, + async (input) => { + try { + const { id } = KitEmailsReadInputSchema.parse(input); + const result = await service.read({ id }); + + const content: TextContent[] = []; + + // Return source, props, and metadata + const { renderedHtml, ...metadata } = result; + + content.push({ type: 'text', text: JSON.stringify(metadata) }); + + // Include rendered HTML as a separate content block + if (renderedHtml) { + content.push({ + type: 'text', + text: `\n\n--- Rendered HTML ---\n\n${renderedHtml}`, + }); + } + + return { + structuredContent: result, + content, + }; + } catch (error) { + return buildErrorResponse('kit_email_templates_read', error); + } + }, + ); +} + +export const registerKitEmailsTools = registerKitEmailTemplatesTools; + +function buildErrorResponse(tool: string, error: unknown) { + const message = `${tool} failed: ${toErrorMessage(error)}`; + + return { + isError: true, + content: buildTextContent(message), + }; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +function buildTextContent(text: string): TextContent[] { + return [{ type: 'text', text }]; +} + +export { createKitEmailsService, createKitEmailsDeps }; +export type { KitEmailsDeps }; +export type { KitEmailsListOutput, KitEmailsReadOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/emails/kit-emails.service.ts b/packages/mcp-server/src/tools/emails/kit-emails.service.ts new file mode 100644 index 000000000..6e490e357 --- /dev/null +++ b/packages/mcp-server/src/tools/emails/kit-emails.service.ts @@ -0,0 +1,289 @@ +import path from 'node:path'; + +import { EMAIL_TEMPLATE_RENDERERS } from '@kit/email-templates/registry'; + +import type { KitEmailsListOutput, KitEmailsReadOutput } from './schema'; + +export interface KitEmailsDeps { + rootPath: string; + readFile(filePath: string): Promise; + readdir(dirPath: string): Promise; + fileExists(filePath: string): Promise; + renderReactEmail( + sampleProps: Record, + templateId: string, + ): Promise; +} + +interface EmailTemplate { + id: string; + name: string; + category: string; + file: string; + description: string; +} + +export function createKitEmailsService(deps: KitEmailsDeps) { + return new KitEmailsService(deps); +} + +export class KitEmailsService { + constructor(private readonly deps: KitEmailsDeps) {} + + async list(): Promise { + const templates: EmailTemplate[] = []; + + const reactTemplates = await this.discoverReactEmailTemplates(); + const supabaseTemplates = await this.discoverSupabaseAuthTemplates(); + + templates.push(...reactTemplates, ...supabaseTemplates); + + const categories = [...new Set(templates.map((t) => t.category))].sort(); + + return { + templates, + categories, + total: templates.length, + }; + } + + async read(input: { id: string }): Promise { + assertSafeId(input.id); + + const { templates } = await this.list(); + const template = templates.find((t) => t.id === input.id); + + if (!template) { + throw new Error(`Email template not found: "${input.id}"`); + } + + const absolutePath = path.resolve(this.deps.rootPath, template.file); + ensureInsideRoot(absolutePath, this.deps.rootPath, input.id); + + const source = await this.deps.readFile(absolutePath); + const isReactEmail = absolutePath.includes('packages/email-templates'); + const props = isReactEmail ? extractPropsFromSource(source) : []; + + let renderedHtml: string | null = null; + + if (isReactEmail) { + const sampleProps = buildSampleProps(props); + + renderedHtml = await this.deps.renderReactEmail(sampleProps, template.id); + } + + return { + id: template.id, + name: template.name, + category: template.category, + file: template.file, + source, + props, + renderedHtml, + }; + } + + private async discoverReactEmailTemplates(): Promise { + const dir = path.join('packages', 'email-templates', 'src', 'emails'); + + const absoluteDir = path.resolve(this.deps.rootPath, dir); + + if (!(await this.deps.fileExists(absoluteDir))) { + return []; + } + + const files = await this.deps.readdir(absoluteDir); + const templates: EmailTemplate[] = []; + + for (const file of files) { + if (!file.endsWith('.email.tsx')) { + continue; + } + + const stem = file.replace(/\.email\.tsx$/, ''); + const id = `${stem}-email`; + const name = humanize(stem); + + templates.push({ + id, + name, + category: 'transactional', + file: path.join(dir, file), + description: `${name} transactional email template`, + }); + } + + return templates.sort((a, b) => a.id.localeCompare(b.id)); + } + + private async discoverSupabaseAuthTemplates(): Promise { + const dir = path.join('apps', 'web', 'supabase', 'templates'); + + const absoluteDir = path.resolve(this.deps.rootPath, dir); + + if (!(await this.deps.fileExists(absoluteDir))) { + return []; + } + + const files = await this.deps.readdir(absoluteDir); + const templates: EmailTemplate[] = []; + + for (const file of files) { + if (!file.endsWith('.html')) { + continue; + } + + const id = file.replace(/\.html$/, ''); + const name = humanize(id); + + templates.push({ + id, + name, + category: 'supabase-auth', + file: path.join(dir, file), + description: `${name} Supabase auth email template`, + }); + } + + return templates.sort((a, b) => a.id.localeCompare(b.id)); + } +} + +function humanize(kebab: string): string { + return kebab + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +function extractPropsFromSource( + source: string, +): Array<{ name: string; type: string; required: boolean }> { + const interfaceMatch = source.match(/interface\s+Props\s*\{([^}]*)\}/); + + if (!interfaceMatch?.[1]) { + return []; + } + + const body = interfaceMatch[1]; + const props: Array<{ name: string; type: string; required: boolean }> = []; + + const propRegex = /(\w+)(\??):\s*([^;\n]+)/g; + let match: RegExpExecArray | null; + + while ((match = propRegex.exec(body)) !== null) { + const name = match[1]!; + const optional = match[2] === '?'; + const type = match[3]!.trim(); + + props.push({ + name, + type, + required: !optional, + }); + } + + return props; +} + +function ensureInsideRoot(resolved: string, root: string, input: string) { + const normalizedRoot = root.endsWith(path.sep) ? root : `${root}${path.sep}`; + + if (!resolved.startsWith(normalizedRoot) && resolved !== root) { + throw new Error( + `Invalid path: "${input}" resolves outside the project root`, + ); + } + + return resolved; +} + +function buildSampleProps( + props: Array<{ name: string; type: string; required: boolean }>, +): Record { + const sample: Record = {}; + + for (const prop of props) { + if (prop.name === 'language') continue; + + sample[prop.name] = SAMPLE_PROP_VALUES[prop.name] ?? `Sample ${prop.name}`; + } + + return sample; +} + +const SAMPLE_PROP_VALUES: Record = { + productName: 'Makerkit', + teamName: 'Acme Team', + inviter: 'John Doe', + invitedUserEmail: 'user@example.com', + link: 'https://example.com/action', + otp: '123456', + email: 'user@example.com', + name: 'Jane Doe', + userName: 'Jane Doe', +}; + +function assertSafeId(id: string) { + if (id.includes('..')) { + throw new Error('Template id must not contain ".."'); + } + + if (id.includes('/') || id.includes('\\')) { + throw new Error('Template id must not include path separators'); + } +} + +export function createKitEmailsDeps(rootPath = process.cwd()): KitEmailsDeps { + return { + rootPath, + async readFile(filePath: string) { + const fs = await import('node:fs/promises'); + return fs.readFile(filePath, 'utf8'); + }, + async readdir(dirPath: string) { + const fs = await import('node:fs/promises'); + return fs.readdir(dirPath); + }, + async fileExists(filePath: string) { + const fs = await import('node:fs/promises'); + + try { + await fs.access(filePath); + return true; + } catch { + return false; + } + }, + async renderReactEmail( + sampleProps: Record, + templateId?: string, + ) { + const renderFromRegistry = + typeof templateId === 'string' + ? EMAIL_TEMPLATE_RENDERERS?.[templateId] + : undefined; + + if (typeof renderFromRegistry === 'function') { + const result = await renderFromRegistry(sampleProps); + + if (typeof result === 'string') { + return result; + } + + if ( + typeof result === 'object' && + result !== null && + 'html' in result && + typeof (result as { html: unknown }).html === 'string' + ) { + return (result as { html: string }).html; + } + + return null; + } + + throw new Error(`Email template renderer not found: "${templateId}"`); + }, + }; +} diff --git a/packages/mcp-server/src/tools/emails/schema.ts b/packages/mcp-server/src/tools/emails/schema.ts new file mode 100644 index 000000000..b603a6816 --- /dev/null +++ b/packages/mcp-server/src/tools/emails/schema.ts @@ -0,0 +1,46 @@ +import { z } from 'zod/v3'; + +export const KitEmailsListInputSchema = z.object({}); + +const EmailTemplateSchema = z.object({ + id: z.string(), + name: z.string(), + category: z.string(), + file: z.string(), + description: z.string(), +}); + +const KitEmailsListSuccessOutputSchema = z.object({ + templates: z.array(EmailTemplateSchema), + categories: z.array(z.string()), + total: z.number(), +}); + +export const KitEmailsListOutputSchema = KitEmailsListSuccessOutputSchema; + +export const KitEmailsReadInputSchema = z.object({ + id: z.string().min(1), +}); + +const PropSchema = z.object({ + name: z.string(), + type: z.string(), + required: z.boolean(), +}); + +const KitEmailsReadSuccessOutputSchema = z.object({ + id: z.string(), + name: z.string(), + category: z.string(), + file: z.string(), + source: z.string(), + props: z.array(PropSchema), + renderedHtml: z.string().nullable(), +}); + +export const KitEmailsReadOutputSchema = KitEmailsReadSuccessOutputSchema; + +export type KitEmailsListInput = z.infer; +export type KitEmailsListOutput = z.infer; +export type KitEmailsReadInput = z.infer; +export type KitEmailsReadOutput = z.infer; diff --git a/packages/mcp-server/src/tools/env/__tests__/kit-env.service.test.ts b/packages/mcp-server/src/tools/env/__tests__/kit-env.service.test.ts new file mode 100644 index 000000000..1102262bd --- /dev/null +++ b/packages/mcp-server/src/tools/env/__tests__/kit-env.service.test.ts @@ -0,0 +1,845 @@ +import { describe, expect, it } from 'vitest'; + +import { type KitEnvDeps, createKitEnvService } from '../kit-env.service'; +import { processEnvDefinitions } from '../scanner'; +import { KitEnvUpdateInputSchema } from '../schema'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function createDeps( + files: Record = {}, + overrides: Partial = {}, +): KitEnvDeps & { _store: Record } { + const store = { ...files }; + + return { + rootPath: '/repo', + async readFile(filePath: string) { + if (!(filePath in store)) { + const error = new Error( + `ENOENT: no such file: ${filePath}`, + ) as NodeJS.ErrnoException; + error.code = 'ENOENT'; + throw error; + } + return store[filePath]!; + }, + async writeFile(filePath: string, content: string) { + store[filePath] = content; + }, + async fileExists(filePath: string) { + return filePath in store; + }, + ...overrides, + get _store() { + return store; + }, + } as KitEnvDeps & { _store: Record }; +} + +// --------------------------------------------------------------------------- +// getSchema +// --------------------------------------------------------------------------- + +describe('KitEnvService.getSchema', () => { + it('returns grouped env variables with expected shape', async () => { + const service = createKitEnvService(createDeps()); + const result = await service.getSchema(); + + expect(result.groups.length).toBeGreaterThan(0); + + for (const group of result.groups) { + expect(group.name).toBeTruthy(); + expect(group.description).toBeTruthy(); + expect(group.variables.length).toBeGreaterThan(0); + + for (const variable of group.variables) { + expect(variable.key).toBeTruthy(); + expect(typeof variable.required).toBe('boolean'); + expect(typeof variable.sensitive).toBe('boolean'); + expect( + ['string', 'url', 'email', 'number', 'boolean', 'enum'].includes( + variable.type, + ), + ).toBe(true); + } + } + }); + + it('includes Stripe variables with dependency metadata', async () => { + const service = createKitEnvService(createDeps()); + const result = await service.getSchema(); + + const billingGroup = result.groups.find((g) => g.name === 'Billing'); + expect(billingGroup).toBeDefined(); + + const stripeSecret = billingGroup!.variables.find( + (v) => v.key === 'STRIPE_SECRET_KEY', + ); + expect(stripeSecret).toBeDefined(); + expect(stripeSecret!.sensitive).toBe(true); + expect(stripeSecret!.dependencies).toBeDefined(); + expect(stripeSecret!.dependencies!.length).toBeGreaterThan(0); + expect(stripeSecret!.dependencies![0]!.variable).toBe( + 'NEXT_PUBLIC_BILLING_PROVIDER', + ); + }); +}); + +// --------------------------------------------------------------------------- +// update +// --------------------------------------------------------------------------- + +describe('KitEnvService.update', () => { + it('replaces an existing key in-place', async () => { + const deps = createDeps({ + '/repo/apps/web/.env.local': + '# Comment\nEMAIL_SENDER=team@example.com\nOTHER=foo\n', + }); + + const service = createKitEnvService(deps); + + const result = await service.update({ + key: 'EMAIL_SENDER', + value: 'hello@example.com', + file: '.env.local', + }); + + expect(result.success).toBe(true); + + const content = deps._store['/repo/apps/web/.env.local']!; + expect(content).toContain('EMAIL_SENDER=hello@example.com'); + // preserves comment + expect(content).toContain('# Comment'); + // preserves other keys + expect(content).toContain('OTHER=foo'); + }); + + it('appends new key when it does not exist', async () => { + const deps = createDeps({ + '/repo/apps/web/.env.local': 'EXISTING=value\n', + }); + + const service = createKitEnvService(deps); + + await service.update({ + key: 'NEW_KEY', + value: 'new_value', + file: '.env.local', + }); + + const content = deps._store['/repo/apps/web/.env.local']!; + expect(content).toContain('EXISTING=value'); + expect(content).toContain('NEW_KEY=new_value'); + }); + + it('creates file if it does not exist', async () => { + const deps = createDeps({}); + + const service = createKitEnvService(deps); + + await service.update({ + key: 'BRAND_NEW', + value: 'value', + file: '.env.local', + }); + + const content = deps._store['/repo/apps/web/.env.local']!; + expect(content).toContain('BRAND_NEW=value'); + }); + + it('throws when key is missing', async () => { + const service = createKitEnvService(createDeps()); + + await expect(service.update({ value: 'v' })).rejects.toThrow( + 'Both key and value are required', + ); + }); + + it('throws when value is missing', async () => { + const service = createKitEnvService(createDeps()); + + await expect(service.update({ key: 'FOO' })).rejects.toThrow( + 'Both key and value are required', + ); + }); + + it('resolves default file for secret key in development mode', async () => { + const deps = createDeps({}); + + const service = createKitEnvService(deps); + + // STRIPE_SECRET_KEY is marked as secret in the model + await service.update({ + key: 'STRIPE_SECRET_KEY', + value: 'sk_test_123', + mode: 'development', + }); + + // Secret keys default to .env.local in dev mode + expect(deps._store['/repo/apps/web/.env.local']).toContain( + 'STRIPE_SECRET_KEY=sk_test_123', + ); + }); + + it('resolves default file for key without explicit secret flag (defaults to secret)', async () => { + const deps = createDeps({}); + + const service = createKitEnvService(deps); + + // NEXT_PUBLIC_SITE_URL has no explicit `secret` field in the model. + // resolveDefaultFile defaults unknown to secret=true (conservative), + // so it should go to .env.local in development mode. + await service.update({ + key: 'NEXT_PUBLIC_SITE_URL', + value: 'http://localhost:3000', + mode: 'development', + }); + + expect(deps._store['/repo/apps/web/.env.local']).toContain( + 'NEXT_PUBLIC_SITE_URL=http://localhost:3000', + ); + }); + + it('resolves default file for secret key in production mode', async () => { + const deps = createDeps({}); + + const service = createKitEnvService(deps); + + await service.update({ + key: 'STRIPE_SECRET_KEY', + value: 'sk_live_abc', + mode: 'production', + }); + + // Secret keys in prod default to .env.production.local + expect(deps._store['/repo/apps/web/.env.production.local']).toContain( + 'STRIPE_SECRET_KEY=sk_live_abc', + ); + }); + + it('does not default file in MCP schema', () => { + const parsed = KitEnvUpdateInputSchema.parse({ + key: 'FOO', + value: 'bar', + mode: 'production', + }); + + expect(parsed.file).toBeUndefined(); + }); +}); + +// --------------------------------------------------------------------------- +// Path traversal prevention +// --------------------------------------------------------------------------- + +describe('KitEnvService — path traversal prevention', () => { + it('rejects file paths that traverse outside web directory', async () => { + const service = createKitEnvService(createDeps()); + + await expect(service.rawRead('../../../../etc/passwd')).rejects.toThrow( + 'resolves outside the web app directory', + ); + }); + + it('rejects rawWrite with traversal path', async () => { + const service = createKitEnvService(createDeps()); + + await expect( + service.rawWrite('../../../etc/evil', 'malicious'), + ).rejects.toThrow('resolves outside the web app directory'); + }); + + it('rejects update with traversal file path', async () => { + const service = createKitEnvService(createDeps()); + + await expect( + service.update({ key: 'FOO', value: 'bar', file: '../../.env' }), + ).rejects.toThrow('resolves outside the web app directory'); + }); + + it('allows valid file names within web directory', async () => { + const deps = createDeps({ + '/repo/apps/web/.env.local': 'KEY=val', + }); + + const service = createKitEnvService(deps); + + const result = await service.rawRead('.env.local'); + expect(result.exists).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// rawRead / rawWrite +// --------------------------------------------------------------------------- + +describe('KitEnvService.rawRead', () => { + it('returns content when file exists', async () => { + const service = createKitEnvService( + createDeps({ + '/repo/apps/web/.env.local': '# My env\nFOO=bar\n', + }), + ); + + const result = await service.rawRead('.env.local'); + + expect(result.exists).toBe(true); + expect(result.content).toBe('# My env\nFOO=bar\n'); + }); + + it('returns empty + exists:false when file missing', async () => { + const service = createKitEnvService(createDeps({})); + + const result = await service.rawRead('.env.local'); + + expect(result.exists).toBe(false); + expect(result.content).toBe(''); + }); +}); + +describe('KitEnvService.rawWrite', () => { + it('overwrites file with raw content', async () => { + const deps = createDeps({ + '/repo/apps/web/.env.local': 'OLD=content', + }); + + const service = createKitEnvService(deps); + + const result = await service.rawWrite('.env.local', '# New\nNEW=value'); + + expect(result.success).toBe(true); + expect(deps._store['/repo/apps/web/.env.local']).toBe('# New\nNEW=value'); + }); + + it('creates file when it does not exist', async () => { + const deps = createDeps({}); + + const service = createKitEnvService(deps); + + await service.rawWrite('.env.production', 'PROD_KEY=val'); + + expect(deps._store['/repo/apps/web/.env.production']).toBe('PROD_KEY=val'); + }); +}); + +// --------------------------------------------------------------------------- +// read — mode-based file precedence +// --------------------------------------------------------------------------- + +describe('KitEnvService.read — file precedence', () => { + it('returns variables with mode information when pointing to real workspace', async () => { + // Use the actual monorepo root — will scan real .env files + const service = createKitEnvService( + createDeps( + {}, + { rootPath: process.cwd().replace(/\/packages\/mcp-server$/, '') }, + ), + ); + + const result = await service.read('development'); + + expect(result.mode).toBe('development'); + expect(typeof result.variables).toBe('object'); + }); +}); + +// --------------------------------------------------------------------------- +// getAppState / getVariable — injected fs +// --------------------------------------------------------------------------- + +describe('KitEnvService — injected fs', () => { + it('getAppState reads from injected fs', async () => { + const deps = createDeps( + { + '/repo/apps/web/.env': 'FOO=bar\n', + }, + { + readdir: async (dirPath: string) => { + if (dirPath === '/repo/apps') { + return ['web']; + } + return []; + }, + stat: async (path: string) => ({ + isDirectory: () => path === '/repo/apps/web', + }), + }, + ); + + const service = createKitEnvService(deps); + const states = await service.getAppState('development'); + + expect(states).toHaveLength(1); + expect(states[0]!.variables['FOO']!.effectiveValue).toBe('bar'); + }); + + it('getVariable reads from injected fs', async () => { + const deps = createDeps( + { + '/repo/apps/web/.env': 'HELLO=world\n', + }, + { + readdir: async (dirPath: string) => { + if (dirPath === '/repo/apps') { + return ['web']; + } + return []; + }, + stat: async (path: string) => ({ + isDirectory: () => path === '/repo/apps/web', + }), + }, + ); + + const service = createKitEnvService(deps); + const value = await service.getVariable('HELLO', 'development'); + + expect(value).toBe('world'); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — override chains +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — override chains', () => { + it('resolves override with development precedence (.env < .env.development < .env.local)', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://base.com', + source: '.env', + }, + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://dev.com', + source: '.env.development', + }, + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://local.com', + source: '.env.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const variable = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(variable).toBeDefined(); + // .env.local has highest precedence in development + expect(variable!.effectiveValue).toBe('https://local.com'); + expect(variable!.effectiveSource).toBe('.env.local'); + expect(variable!.isOverridden).toBe(true); + expect(variable!.definitions).toHaveLength(3); + }); + + it('resolves override with production precedence (.env < .env.production < .env.local < .env.production.local)', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://base.com', + source: '.env', + }, + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://prod.com', + source: '.env.production', + }, + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://local.com', + source: '.env.local', + }, + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://prod-local.com', + source: '.env.production.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'production'); + const variable = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(variable!.effectiveValue).toBe('https://prod-local.com'); + expect(variable!.effectiveSource).toBe('.env.production.local'); + expect(variable!.isOverridden).toBe(true); + }); + + it('marks single-source variable as NOT overridden', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://site.com', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const variable = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(variable!.isOverridden).toBe(false); + expect(variable!.effectiveSource).toBe('.env'); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — conditional requirements (Stripe keys) +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — conditional requirements', () => { + it('flags STRIPE_SECRET_KEY as invalid when billing provider is stripe and key is missing', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'stripe', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + + const stripeKey = result.variables['STRIPE_SECRET_KEY']; + expect(stripeKey).toBeDefined(); + expect(stripeKey!.effectiveSource).toBe('MISSING'); + expect(stripeKey!.validation.success).toBe(false); + expect(stripeKey!.validation.error.issues.length).toBeGreaterThan(0); + + // Regression guard: contextual message must be preserved, NOT replaced + // by generic "required but missing" + expect( + stripeKey!.validation.error.issues.some((i) => + i.includes('NEXT_PUBLIC_BILLING_PROVIDER'), + ), + ).toBe(true); + }); + + it('does NOT flag STRIPE_SECRET_KEY when billing provider is lemon-squeezy', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'lemon-squeezy', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + + const stripeKey = result.variables['STRIPE_SECRET_KEY']; + expect(stripeKey).toBeUndefined(); + }); + + it('flags LEMON_SQUEEZY_SECRET_KEY as invalid when provider is lemon-squeezy and key is missing', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'lemon-squeezy', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + + const lsKey = result.variables['LEMON_SQUEEZY_SECRET_KEY']; + expect(lsKey).toBeDefined(); + expect(lsKey!.effectiveSource).toBe('MISSING'); + expect(lsKey!.validation.success).toBe(false); + }); + + it('validates Stripe key format (must start with sk_ or rk_)', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'stripe', + source: '.env', + }, + { + key: 'STRIPE_SECRET_KEY', + value: 'invalid_key_123', + source: '.env.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const stripeKey = result.variables['STRIPE_SECRET_KEY']; + + expect(stripeKey).toBeDefined(); + expect(stripeKey!.validation.success).toBe(false); + expect( + stripeKey!.validation.error.issues.some( + (i) => + i.toLowerCase().includes('sk_') || i.toLowerCase().includes('rk_'), + ), + ).toBe(true); + }); + + it('passes Stripe key validation when key format is correct', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'stripe', + source: '.env', + }, + { + key: 'STRIPE_SECRET_KEY', + value: 'sk_test_abc123', + source: '.env.local', + }, + { + key: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', + value: 'pk_test_abc123', + source: '.env', + }, + { + key: 'STRIPE_WEBHOOK_SECRET', + value: 'whsec_abc123', + source: '.env.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const stripeKey = result.variables['STRIPE_SECRET_KEY']; + + expect(stripeKey!.validation.success).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — cross-variable validations +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — cross-variable validations', () => { + it('flags SUPABASE_SERVICE_ROLE_KEY when same as ANON_KEY', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SUPABASE_URL', + value: 'http://localhost:54321', + source: '.env', + }, + { + key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', + value: 'same-key', + source: '.env', + }, + { + key: 'SUPABASE_SERVICE_ROLE_KEY', + value: 'same-key', + source: '.env.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const serviceKey = result.variables['SUPABASE_SERVICE_ROLE_KEY']; + + expect(serviceKey).toBeDefined(); + expect(serviceKey!.validation.success).toBe(false); + expect( + serviceKey!.validation.error.issues.some((i) => + i.toLowerCase().includes('different'), + ), + ).toBe(true); + }); + + it('passes when SUPABASE_SERVICE_ROLE_KEY differs from ANON_KEY', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SUPABASE_URL', + value: 'http://localhost:54321', + source: '.env', + }, + { + key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', + value: 'anon-key-123', + source: '.env', + }, + { + key: 'SUPABASE_SERVICE_ROLE_KEY', + value: 'service-key-456', + source: '.env.local', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const serviceKey = result.variables['SUPABASE_SERVICE_ROLE_KEY']; + + expect(serviceKey!.validation.success).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — mode-aware URL validation +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — mode-aware validations', () => { + it('accepts http:// SITE_URL in development mode', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'http://localhost:3000', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const siteUrl = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(siteUrl!.validation.success).toBe(true); + }); + + it('rejects http:// SITE_URL in production mode', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'http://example.com', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'production'); + const siteUrl = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(siteUrl!.validation.success).toBe(false); + expect( + siteUrl!.validation.error.issues.some((i) => + i.toLowerCase().includes('https'), + ), + ).toBe(true); + }); + + it('accepts https:// SITE_URL in production mode', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_SITE_URL', + value: 'https://example.com', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'production'); + const siteUrl = result.variables['NEXT_PUBLIC_SITE_URL']; + + expect(siteUrl!.validation.success).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — missing required variables +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — missing required variables', () => { + it('injects missing required variables with MISSING source', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_BILLING_PROVIDER', + value: 'stripe', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + + // NEXT_PUBLIC_SITE_URL is required and missing + const siteUrl = result.variables['NEXT_PUBLIC_SITE_URL']; + expect(siteUrl).toBeDefined(); + expect(siteUrl!.effectiveSource).toBe('MISSING'); + expect(siteUrl!.validation.success).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// processEnvDefinitions — CAPTCHA conditional dependency +// --------------------------------------------------------------------------- + +describe('processEnvDefinitions — CAPTCHA conditional dependency', () => { + it('flags CAPTCHA_SECRET_TOKEN as required when CAPTCHA_SITE_KEY is set', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [ + { + key: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', + value: 'cap_site_123', + source: '.env', + }, + ], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + const captchaSecret = result.variables['CAPTCHA_SECRET_TOKEN']; + + expect(captchaSecret).toBeDefined(); + expect(captchaSecret!.effectiveSource).toBe('MISSING'); + expect(captchaSecret!.validation.success).toBe(false); + }); + + it('does NOT flag CAPTCHA_SECRET_TOKEN when CAPTCHA_SITE_KEY is empty/absent', () => { + const envInfo = { + appName: 'web', + filePath: '/repo/apps/web', + variables: [], + }; + + const result = processEnvDefinitions(envInfo, 'development'); + + const captchaSecret = result.variables['CAPTCHA_SECRET_TOKEN']; + expect(captchaSecret).toBeUndefined(); + }); +}); diff --git a/packages/mcp-server/src/tools/env/index.ts b/packages/mcp-server/src/tools/env/index.ts new file mode 100644 index 000000000..eb4c71f83 --- /dev/null +++ b/packages/mcp-server/src/tools/env/index.ts @@ -0,0 +1,177 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; + +import { + type KitEnvDeps, + createKitEnvDeps, + createKitEnvService, +} from './kit-env.service'; +import { + KitEnvRawReadInputSchema, + KitEnvRawReadOutputSchema, + KitEnvRawWriteInputSchema, + KitEnvRawWriteOutputSchema, + KitEnvReadInputSchema, + KitEnvReadOutputSchema, + KitEnvSchemaInputSchema, + KitEnvSchemaOutputSchema, + KitEnvUpdateInputSchema, + KitEnvUpdateOutputSchema, +} from './schema'; + +type TextContent = { + type: 'text'; + text: string; +}; + +export function registerKitEnvTools(server: McpServer) { + const service = createKitEnvService(createKitEnvDeps()); + + server.registerTool( + 'kit_env_schema', + { + description: 'Return environment variable schema for this kit variant', + inputSchema: KitEnvSchemaInputSchema, + outputSchema: KitEnvSchemaOutputSchema, + }, + async () => { + try { + const result = await service.getSchema(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_env_schema', error); + } + }, + ); + + server.registerTool( + 'kit_env_read', + { + description: 'Read environment variables and validation state', + inputSchema: KitEnvReadInputSchema, + outputSchema: KitEnvReadOutputSchema, + }, + async (input) => { + try { + const parsed = KitEnvReadInputSchema.parse(input); + const result = await service.read(parsed.mode); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_env_read', error); + } + }, + ); + + server.registerTool( + 'kit_env_update', + { + description: 'Update one environment variable in a target .env file', + inputSchema: KitEnvUpdateInputSchema, + outputSchema: KitEnvUpdateOutputSchema, + }, + async (input) => { + try { + const parsed = KitEnvUpdateInputSchema.parse(input); + const result = await service.update(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_env_update', error); + } + }, + ); + + server.registerTool( + 'kit_env_raw_read', + { + description: 'Read raw content of an .env file', + inputSchema: KitEnvRawReadInputSchema, + outputSchema: KitEnvRawReadOutputSchema, + }, + async (input) => { + try { + const parsed = KitEnvRawReadInputSchema.parse(input); + const result = await service.rawRead(parsed.file); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_env_raw_read', error); + } + }, + ); + + server.registerTool( + 'kit_env_raw_write', + { + description: 'Write raw content to an .env file', + inputSchema: KitEnvRawWriteInputSchema, + outputSchema: KitEnvRawWriteOutputSchema, + }, + async (input) => { + try { + const parsed = KitEnvRawWriteInputSchema.parse(input); + const result = await service.rawWrite(parsed.file, parsed.content); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_env_raw_write', error); + } + }, + ); +} + +function buildErrorResponse(tool: string, error: unknown) { + const message = `${tool} failed: ${toErrorMessage(error)}`; + + return { + isError: true, + content: buildTextContent(message), + }; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +function buildTextContent(text: string): TextContent[] { + return [{ type: 'text', text }]; +} + +export { + createKitEnvService, + createKitEnvDeps, + envVariables, + findWorkspaceRoot, + scanMonorepoEnv, + processEnvDefinitions, + getEnvState, + getVariable, +} from './public-api'; +export type { KitEnvDeps }; +export type { EnvVariableModel } from './model'; +export type { + EnvMode, + AppEnvState, + EnvFileInfo, + EnvVariableState, +} from './types'; diff --git a/packages/mcp-server/src/tools/env/kit-env.service.ts b/packages/mcp-server/src/tools/env/kit-env.service.ts new file mode 100644 index 000000000..75a45ec2c --- /dev/null +++ b/packages/mcp-server/src/tools/env/kit-env.service.ts @@ -0,0 +1,320 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import { envVariables } from './model'; +import { getEnvState } from './scanner'; +import type { EnvMode, ScanFs } from './types'; + +export interface KitEnvDeps { + rootPath: string; + readFile(filePath: string): Promise; + writeFile(filePath: string, content: string): Promise; + fileExists(filePath: string): Promise; + readdir?(dirPath: string): Promise; + stat?(path: string): Promise<{ isDirectory(): boolean }>; +} + +export function createKitEnvService(deps: KitEnvDeps) { + return new KitEnvService(deps); +} + +export class KitEnvService { + constructor(private readonly deps: KitEnvDeps) {} + + async getSchema() { + const groups = new Map< + string, + Array<{ + key: string; + label: string; + description: string; + required: boolean; + type: 'string' | 'url' | 'email' | 'number' | 'boolean' | 'enum'; + sensitive: boolean; + values?: string[]; + hint?: string; + dependencies?: Array<{ variable: string; condition: string }>; + }> + >(); + + for (const variable of envVariables) { + const category = variable.category; + + if (!groups.has(category)) { + groups.set(category, []); + } + + groups.get(category)!.push({ + key: variable.name, + label: variable.displayName, + description: variable.description, + required: variable.required ?? false, + type: mapType(variable.type), + sensitive: variable.secret ?? false, + values: variable.values?.filter( + (v): v is string => typeof v === 'string', + ), + hint: variable.hint, + dependencies: variable.contextualValidation?.dependencies.map( + (dep) => ({ + variable: dep.variable, + condition: dep.message, + }), + ), + }); + } + + return { + groups: Array.from(groups.entries()).map(([name, variables]) => ({ + name, + description: `${name} configuration`, + variables, + })), + }; + } + + async read(mode: EnvMode) { + const scanFs = this.getScanFs(); + const states = await getEnvState({ + mode, + apps: ['web'], + rootDir: this.deps.rootPath, + fs: scanFs, + }); + + const webState = states.find((state) => state.appName === 'web'); + + if (!webState) { + return { + mode, + variables: {}, + }; + } + + const allVariables = Object.values(webState.variables).reduce( + (acc, variable) => { + acc[variable.key] = variable.effectiveValue; + return acc; + }, + {} as Record, + ); + + const variables = Object.fromEntries( + Object.entries(webState.variables).map(([key, variable]) => { + const model = envVariables.find((item) => item.name === key); + + return [ + key, + { + key, + value: variable.effectiveValue, + source: variable.effectiveSource, + isOverridden: variable.isOverridden, + overrideChain: + variable.definitions.length > 1 + ? variable.definitions.map((definition) => ({ + source: definition.source, + value: definition.value, + })) + : undefined, + validation: { + valid: variable.validation.success, + errors: variable.validation.error.issues, + }, + dependencies: model?.contextualValidation?.dependencies.map( + (dep) => { + const dependencyValue = allVariables[dep.variable] ?? ''; + const satisfied = dep.condition(dependencyValue, allVariables); + + return { + variable: dep.variable, + condition: dep.message, + satisfied, + }; + }, + ), + }, + ]; + }), + ); + + return { + mode, + variables, + }; + } + + async update(input: { + key?: string; + value?: string; + file?: string; + mode?: EnvMode; + }) { + if (!input.key || typeof input.value !== 'string') { + throw new Error('Both key and value are required for kit_env_update'); + } + + const fileName = + input.file ?? + this.resolveDefaultFile(input.key, input.mode ?? 'development'); + const targetPath = this.resolveWebFile(fileName); + + let content = ''; + + if (await this.deps.fileExists(targetPath)) { + content = await this.deps.readFile(targetPath); + } + + const lines = content.length > 0 ? content.split('\n') : []; + + let replaced = false; + const updatedLines = lines.map((line) => { + if (line.startsWith(`${input.key}=`)) { + replaced = true; + return `${input.key}=${input.value}`; + } + + return line; + }); + + if (!replaced) { + if ( + updatedLines.length > 0 && + updatedLines[updatedLines.length - 1] !== '' + ) { + updatedLines.push(''); + } + + updatedLines.push(`${input.key}=${input.value}`); + } + + await this.deps.writeFile(targetPath, updatedLines.join('\n')); + + return { + success: true, + message: `Updated ${input.key} in ${fileName}`, + }; + } + + async rawRead(file: string) { + const targetPath = this.resolveWebFile(file); + + if (!(await this.deps.fileExists(targetPath))) { + return { + content: '', + exists: false, + }; + } + + return { + content: await this.deps.readFile(targetPath), + exists: true, + }; + } + + async rawWrite(file: string, content: string) { + const targetPath = this.resolveWebFile(file); + await this.deps.writeFile(targetPath, content); + + return { + success: true, + message: `Saved ${file}`, + }; + } + + async getVariable(key: string, mode: EnvMode) { + const result = await this.read(mode); + return result.variables[key]?.value ?? ''; + } + + async getAppState(mode: EnvMode) { + const scanFs = this.getScanFs(); + const states = await getEnvState({ + mode, + apps: ['web'], + rootDir: this.deps.rootPath, + fs: scanFs, + }); + + return states; + } + + private resolveDefaultFile(key: string, mode: EnvMode) { + const model = envVariables.find((item) => item.name === key); + const isSecret = model?.secret ?? true; + + if (mode === 'production') { + return isSecret ? '.env.production.local' : '.env.production'; + } + + return isSecret ? '.env.local' : '.env.development'; + } + + private resolveWebFile(fileName: string) { + const webDir = path.resolve(this.deps.rootPath, 'apps', 'web'); + const resolved = path.resolve(webDir, fileName); + + // Prevent path traversal outside the web app directory + if (!resolved.startsWith(webDir + path.sep) && resolved !== webDir) { + throw new Error( + `Invalid file path: "${fileName}" resolves outside the web app directory`, + ); + } + + return resolved; + } + + private getScanFs(): ScanFs | undefined { + if (!this.deps.readdir || !this.deps.stat) { + return undefined; + } + + return { + readFile: (filePath) => this.deps.readFile(filePath), + readdir: (dirPath) => this.deps.readdir!(dirPath), + stat: (path) => this.deps.stat!(path), + }; + } +} + +function mapType( + type?: string, +): 'string' | 'url' | 'email' | 'number' | 'boolean' | 'enum' { + if ( + type === 'url' || + type === 'email' || + type === 'number' || + type === 'boolean' || + type === 'enum' + ) { + return type; + } + + return 'string'; +} + +export function createKitEnvDeps(rootPath = process.cwd()): KitEnvDeps { + return { + rootPath, + readFile(filePath: string) { + return fs.readFile(filePath, 'utf8'); + }, + writeFile(filePath: string, content: string) { + return fs.writeFile(filePath, content, 'utf8'); + }, + readdir(dirPath: string) { + return fs.readdir(dirPath); + }, + stat(path: string) { + return fs.stat(path); + }, + async fileExists(filePath: string) { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } + }, + }; +} diff --git a/packages/mcp-server/src/tools/env/model.ts b/packages/mcp-server/src/tools/env/model.ts new file mode 100644 index 000000000..ec5aff2bf --- /dev/null +++ b/packages/mcp-server/src/tools/env/model.ts @@ -0,0 +1,1430 @@ +import { z } from 'zod/v3'; + +import type { EnvMode } from './types'; + +type ModelType = + | 'string' + | 'longString' + | 'number' + | 'boolean' + | 'enum' + | 'url' + | 'email'; + +type Values = Array; + +export type EnvVariableModel = { + name: string; + displayName: string; + description: string; + hint?: string; + secret?: boolean; + type?: ModelType; + values?: Values; + category: string; + required?: boolean; + deprecated?: { + reason: string; + alternative?: string; + }; + validate?: ({ + value, + variables, + mode, + }: { + value: string; + variables: Record; + mode: EnvMode; + }) => z.SafeParseReturnType; + contextualValidation?: { + dependencies: Array<{ + variable: string; + condition: (value: string, variables: Record) => boolean; + message: string; + }>; + validate: ({ + value, + variables, + mode, + }: { + value: string; + variables: Record; + mode: EnvMode; + }) => z.SafeParseReturnType; + }; +}; + +export const envVariables: EnvVariableModel[] = [ + { + name: 'NEXT_PUBLIC_SITE_URL', + displayName: 'Site URL', + description: + 'The URL of your site, used for generating absolute URLs. Must include the protocol.', + category: 'Site Configuration', + required: true, + type: 'url', + hint: `Ex. https://example.com`, + validate: ({ value, mode }) => { + if (mode === 'development') { + return z + .string() + .url({ + message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`, + }) + .safeParse(value); + } + + return z + .string() + .url({ + message: `The NEXT_PUBLIC_SITE_URL variable must be a valid URL`, + }) + .startsWith( + 'https', + `The NEXT_PUBLIC_SITE_URL variable must start with https`, + ) + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_PRODUCT_NAME', + displayName: 'Product Name', + description: + "Your product's name, used consistently across the application interface.", + category: 'Site Configuration', + hint: `Ex. "My Product"`, + required: true, + type: 'string', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_PRODUCT_NAME variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SITE_TITLE', + displayName: 'Site Title', + description: + "The site's title tag content, crucial for SEO and browser display.", + category: 'Site Configuration', + required: true, + hint: `Ex. "My Product, the best product ever"`, + type: 'string', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_SITE_TITLE variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SITE_DESCRIPTION', + displayName: 'Site Description', + type: 'longString', + description: + "Your site's meta description, important for SEO optimization.", + category: 'Site Configuration', + required: true, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_SITE_DESCRIPTION variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_DEFAULT_LOCALE', + displayName: 'Default Locale', + type: 'string', + description: 'Sets the default language for your application.', + category: 'Localization', + hint: `Ex. "en"`, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_DEFAULT_LOCALE variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_AUTH_PASSWORD', + displayName: 'Enable Password Authentication', + description: 'Enables or disables password-based authentication.', + category: 'Authentication', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_AUTH_MAGIC_LINK', + displayName: 'Enable Magic Link Authentication', + description: 'Enables or disables magic link authentication.', + category: 'Authentication', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_AUTH_IDENTITY_LINKING', + displayName: 'Enable Identity Linking', + description: 'Allows users to link multiple auth identities.', + category: 'Authentication', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_AUTH_OTP', + displayName: 'Enable OTP Authentication', + description: 'Enables or disables one-time password authentication.', + category: 'Authentication', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', + displayName: 'Captcha Site Key', + description: 'Your Cloudflare Captcha site key for form protection.', + category: 'Security', + type: 'string', + validate: ({ value }) => { + return z.string().optional().safeParse(value); + }, + }, + { + name: 'CAPTCHA_SECRET_TOKEN', + displayName: 'Captcha Secret Token', + description: + 'Your Cloudflare Captcha secret token for backend verification.', + category: 'Security', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_CAPTCHA_SITE_KEY', + condition: (value) => { + return value !== ''; + }, + message: + 'CAPTCHA_SECRET_TOKEN is required when NEXT_PUBLIC_CAPTCHA_SITE_KEY is set', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The CAPTCHA_SECRET_TOKEN variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_USER_NAVIGATION_STYLE', + displayName: 'User Navigation Style', + description: + 'Controls user navigation layout. Options: sidebar, header, or custom.', + category: 'Navigation', + type: 'enum', + values: ['sidebar', 'header', 'custom'], + validate: ({ value }) => { + return z + .enum(['sidebar', 'header', 'custom']) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED', + displayName: 'Home Sidebar Collapsed', + description: 'Sets the default state of the home sidebar.', + category: 'Navigation', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_TEAM_NAVIGATION_STYLE', + displayName: 'Team Navigation Style', + description: + 'Controls team navigation layout. Options: sidebar, header, or custom.', + category: 'Navigation', + type: 'enum', + values: ['sidebar', 'header', 'custom'], + validate: ({ value }) => { + return z + .enum(['sidebar', 'header', 'custom']) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED', + displayName: 'Team Sidebar Collapsed', + description: 'Sets the default state of the team sidebar.', + category: 'Navigation', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE', + displayName: 'Sidebar Collapsible Style', + description: + 'Defines sidebar collapse behavior. Options: offcanvas, icon, or none.', + category: 'Navigation', + type: 'enum', + values: ['offcanvas', 'icon', 'none'], + validate: ({ value }) => { + return z.enum(['offcanvas', 'icon', 'none']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_DEFAULT_THEME_MODE', + displayName: 'Default Theme Mode', + description: + 'Controls the default theme appearance. Options: light, dark, or system.', + category: 'Theme', + type: 'enum', + values: ['light', 'dark', 'system'], + validate: ({ value }) => { + return z.enum(['light', 'dark', 'system']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_THEME_TOGGLE', + displayName: 'Enable Theme Toggle', + description: 'Controls visibility of the theme toggle feature.', + category: 'Theme', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER', + displayName: 'Enable Sidebar Trigger', + description: 'Controls visibility of the sidebar trigger feature.', + category: 'Navigation', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION', + displayName: 'Enable Personal Account Deletion', + description: 'Allows users to delete their personal accounts.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING', + displayName: 'Enable Personal Account Billing', + description: 'Enables billing features for personal accounts.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS', + displayName: 'Enable Team Accounts', + description: 'Master switch for team account functionality.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION', + displayName: 'Enable Team Account Creation', + description: 'Controls ability to create new team accounts.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION', + displayName: 'Enable Team Account Deletion', + description: 'Allows team account deletion.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING', + displayName: 'Enable Team Account Billing', + description: 'Enables billing features for team accounts.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_NOTIFICATIONS', + displayName: 'Enable Notifications', + description: 'Controls the notification system.', + category: 'Notifications', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_REALTIME_NOTIFICATIONS', + displayName: 'Enable Realtime Notifications', + description: 'Enables real-time notifications using Supabase Realtime.', + category: 'Notifications', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SUPABASE_URL', + displayName: 'Supabase URL', + description: 'Your Supabase project URL.', + category: 'Supabase', + hint: `Ex. https://your-project.supabase.co`, + required: true, + type: 'url', + validate: ({ value, mode }) => { + if (mode === 'development') { + return z + .string() + .url({ + message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`, + }) + .safeParse(value); + } + + return z + .string() + .url({ + message: `The NEXT_PUBLIC_SUPABASE_URL variable must be a valid URL`, + }) + .startsWith( + 'https', + `The NEXT_PUBLIC_SUPABASE_URL variable must start with https`, + ) + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', + displayName: 'Supabase Anonymous Key', + description: 'Your Supabase anonymous API key.', + category: 'Supabase', + type: 'string', + deprecated: { + reason: 'Replaced by new JWT signing key system', + alternative: 'NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_SUPABASE_ANON_KEY variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SUPABASE_PUBLIC_KEY', + displayName: 'Supabase Public Key', + description: 'Your Supabase public API key.', + category: 'Supabase', + required: false, + type: 'string', + hint: 'Falls back to NEXT_PUBLIC_SUPABASE_ANON_KEY if not provided', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_SUPABASE_PUBLIC_KEY variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'SUPABASE_SERVICE_ROLE_KEY', + displayName: 'Supabase Service Role Key', + description: 'Your Supabase service role key (keep this secret!).', + category: 'Supabase', + secret: true, + type: 'string', + deprecated: { + reason: 'Renamed for consistency with new JWT signing key system', + alternative: 'SUPABASE_SECRET_KEY', + }, + validate: ({ value, variables }) => { + return z + .string() + .min( + 1, + `The SUPABASE_SERVICE_ROLE_KEY variable must be at least 1 character`, + ) + .refine( + (value) => { + return value !== variables['NEXT_PUBLIC_SUPABASE_ANON_KEY']; + }, + { + message: `The SUPABASE_SERVICE_ROLE_KEY variable must be different from NEXT_PUBLIC_SUPABASE_ANON_KEY`, + }, + ) + .safeParse(value); + }, + }, + { + name: 'SUPABASE_SECRET_KEY', + displayName: 'Supabase Secret Key', + description: + 'Your Supabase secret key (preferred over SUPABASE_SERVICE_ROLE_KEY).', + category: 'Supabase', + secret: true, + required: false, + type: 'string', + hint: 'Falls back to SUPABASE_SERVICE_ROLE_KEY if not provided', + validate: ({ value, variables }) => { + return z + .string() + .min(1, `The SUPABASE_SECRET_KEY variable must be at least 1 character`) + .refine( + (value) => { + const anonKey = + variables['NEXT_PUBLIC_SUPABASE_ANON_KEY'] || + variables['NEXT_PUBLIC_SUPABASE_PUBLIC_KEY']; + return value !== anonKey; + }, + { + message: `The SUPABASE_SECRET_KEY variable must be different from public keys`, + }, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'SUPABASE_DB_WEBHOOK_SECRET', + displayName: 'Supabase Database Webhook Secret', + description: 'Secret key for Supabase webhook verification.', + category: 'Supabase', + secret: true, + required: true, + type: 'string', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The SUPABASE_DB_WEBHOOK_SECRET variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_BILLING_PROVIDER', + displayName: 'Billing Provider', + description: + 'Your chosen billing provider. Options: stripe or lemon-squeezy.', + category: 'Billing', + required: true, + type: 'enum', + values: ['stripe', 'lemon-squeezy'], + validate: ({ value }) => { + return z.enum(['stripe', 'lemon-squeezy']).optional().safeParse(value); + }, + }, + { + name: 'BILLING_MODE', + displayName: 'Billing Mode', + description: 'Billing mode configuration for the application.', + category: 'Billing', + required: false, + type: 'enum', + values: ['subscription', 'one-time'], + deprecated: { + reason: + 'This configuration is no longer required and billing mode is now automatically determined', + alternative: undefined, + }, + validate: ({ value }) => { + return z.enum(['subscription', 'one-time']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', + displayName: 'Stripe Publishable Key', + description: 'Your Stripe publishable key.', + hint: `Ex. pk_test_123456789012345678901234`, + category: 'Billing', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'stripe', + message: + 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`, + ) + .refine( + (value) => { + return value.startsWith('pk_'); + }, + { + message: `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must start with pk_`, + }, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'STRIPE_SECRET_KEY', + displayName: 'Stripe Secret Key', + description: 'Your Stripe secret key.', + category: 'Billing', + hint: `Ex. sk_test_123456789012345678901234`, + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'stripe', + message: + 'STRIPE_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`) + .refine( + (value) => { + return value.startsWith('sk_') || value.startsWith('rk_'); + }, + { + message: `The STRIPE_SECRET_KEY variable must start with sk_ or rk_`, + }, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min(1, `The STRIPE_SECRET_KEY variable must be at least 1 character`) + .optional() + .safeParse(value); + }, + }, + { + name: 'STRIPE_WEBHOOK_SECRET', + displayName: 'Stripe Webhook Secret', + description: 'Your Stripe webhook secret.', + category: 'Billing', + hint: `Ex. whsec_123456789012345678901234`, + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'stripe', + message: + 'STRIPE_WEBHOOK_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "stripe"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`, + ) + .refine( + (value) => { + return value.startsWith('whsec_'); + }, + { + message: `The STRIPE_WEBHOOK_SECRET variable must start with whsec_`, + }, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The STRIPE_WEBHOOK_SECRET variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'LEMON_SQUEEZY_SECRET_KEY', + displayName: 'Lemon Squeezy Secret Key', + description: 'Your Lemon Squeezy secret key.', + category: 'Billing', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'lemon-squeezy', + message: + 'LEMON_SQUEEZY_SECRET_KEY is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_SECRET_KEY variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'LEMON_SQUEEZY_STORE_ID', + displayName: 'Lemon Squeezy Store ID', + description: 'Your Lemon Squeezy store ID.', + category: 'Billing', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'lemon-squeezy', + message: + 'LEMON_SQUEEZY_STORE_ID is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_STORE_ID variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'LEMON_SQUEEZY_SIGNING_SECRET', + displayName: 'Lemon Squeezy Signing Secret', + description: 'Your Lemon Squeezy signing secret.', + category: 'Billing', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_BILLING_PROVIDER', + condition: (value) => value === 'lemon-squeezy', + message: + 'LEMON_SQUEEZY_SIGNING_SECRET is required when NEXT_PUBLIC_BILLING_PROVIDER is set to "lemon-squeezy"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The LEMON_SQUEEZY_SIGNING_SECRET variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'MAILER_PROVIDER', + displayName: 'Mailer Provider', + description: 'Your email service provider. Options: nodemailer or resend.', + category: 'Email', + required: true, + type: 'enum', + values: ['nodemailer', 'resend'], + validate: ({ value }) => { + return z.enum(['nodemailer', 'resend']).safeParse(value); + }, + }, + { + name: 'EMAIL_SENDER', + displayName: 'Email Sender', + description: 'Default sender email address.', + category: 'Email', + hint: `Ex. "Makerkit "`, + required: true, + type: 'string', + validate: ({ value }) => { + return z + .string() + .min(1, `The EMAIL_SENDER variable must be at least 1 character`) + .safeParse(value); + }, + }, + { + name: 'CONTACT_EMAIL', + displayName: 'Contact Email', + description: 'Email address for contact form submissions.', + category: 'Email', + hint: `Ex. "Makerkit "`, + required: true, + type: 'email', + validate: ({ value }) => { + return z + .string() + .email() + .min(1, `The CONTACT_EMAIL variable must be at least 1 character`) + .safeParse(value); + }, + }, + { + name: 'RESEND_API_KEY', + displayName: 'Resend API Key', + description: 'Your Resend API key.', + category: 'Email', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'resend', + message: + 'RESEND_API_KEY is required when MAILER_PROVIDER is set to "resend"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'resend') { + return z + .string() + .min(1, `The RESEND_API_KEY variable must be at least 1 character`) + .safeParse(value); + } + + return z.string().optional().safeParse(value); + }, + }, + }, + { + name: 'EMAIL_HOST', + displayName: 'Email Host', + description: 'SMTP host for Nodemailer configuration.', + category: 'Email', + type: 'string', + hint: `Ex. "smtp.example.com"`, + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'nodemailer', + message: + 'EMAIL_HOST is required when MAILER_PROVIDER is set to "nodemailer"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'nodemailer') { + return z + .string() + .min(1, 'The EMAIL_HOST variable must be at least 1 character') + .safeParse(value); + } + return z.string().optional().safeParse(value); + }, + }, + }, + { + name: 'EMAIL_PORT', + displayName: 'Email Port', + description: 'SMTP port for Nodemailer configuration.', + category: 'Email', + type: 'number', + hint: `Ex. 587 or 465`, + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'nodemailer', + message: + 'EMAIL_PORT is required when MAILER_PROVIDER is set to "nodemailer"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'nodemailer') { + return z.coerce + .number() + .min(1, 'The EMAIL_PORT variable must be at least 1') + .max(65535, 'The EMAIL_PORT variable must be at most 65535') + .safeParse(value); + } + return z.coerce.number().optional().safeParse(value); + }, + }, + }, + { + name: 'EMAIL_USER', + displayName: 'Email User', + description: 'SMTP user for Nodemailer configuration.', + category: 'Email', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'nodemailer', + message: + 'EMAIL_USER is required when MAILER_PROVIDER is set to "nodemailer"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'nodemailer') { + return z + .string() + .min(1, 'The EMAIL_USER variable must be at least 1 character') + .safeParse(value); + } + + return z.string().optional().safeParse(value); + }, + }, + }, + { + name: 'EMAIL_PASSWORD', + displayName: 'Email Password', + description: 'SMTP password for Nodemailer configuration.', + category: 'Email', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'nodemailer', + message: + 'EMAIL_PASSWORD is required when MAILER_PROVIDER is set to "nodemailer"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'nodemailer') { + return z + .string() + .min(1, 'The EMAIL_PASSWORD variable must be at least 1 character') + .safeParse(value); + } + return z.string().optional().safeParse(value); + }, + }, + }, + { + name: 'EMAIL_TLS', + displayName: 'Email TLS', + description: + 'Whether to use TLS for SMTP connection. Please check this in your SMTP provider settings.', + category: 'Email', + type: 'boolean', + contextualValidation: { + dependencies: [ + { + variable: 'MAILER_PROVIDER', + condition: (value) => value === 'nodemailer', + message: + 'EMAIL_TLS is required when MAILER_PROVIDER is set to "nodemailer"', + }, + ], + validate: ({ value, variables }) => { + if (variables['MAILER_PROVIDER'] === 'nodemailer') { + return z.coerce.boolean().optional().safeParse(value); + } + return z.coerce.boolean().optional().safeParse(value); + }, + }, + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'CMS_CLIENT', + displayName: 'CMS Client', + description: 'Your chosen CMS system. Options: wordpress or keystatic.', + category: 'CMS', + type: 'enum', + values: ['wordpress', 'keystatic'], + validate: ({ value }) => { + return z.enum(['wordpress', 'keystatic']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND', + displayName: 'Keystatic Storage Kind', + description: 'Your Keystatic storage kind. Options: local, cloud, github.', + category: 'CMS', + type: 'enum', + values: ['local', 'cloud', 'github'], + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value) => value === 'keystatic', + message: + 'NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND is required when CMS_CLIENT is set to "keystatic"', + }, + ], + validate: ({ value, variables }) => { + if (variables['CMS_CLIENT'] === 'keystatic') { + return z + .enum(['local', 'cloud', 'github']) + .optional() + .safeParse(value); + } + return z.enum(['local', 'cloud', 'github']).optional().safeParse(value); + }, + }, + }, + { + name: 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO', + displayName: 'Keystatic Storage Repository', + description: 'Your Keystatic storage repo.', + category: 'CMS', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value, variables) => + value === 'keystatic' && + variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github', + message: + 'NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO is required when CMS_CLIENT is set to "keystatic"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + }, + { + name: 'KEYSTATIC_GITHUB_TOKEN', + displayName: 'Keystatic GitHub Token', + description: 'Your Keystatic GitHub token.', + category: 'CMS', + secret: true, + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value, variables) => + value === 'keystatic' && + variables['NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND'] === 'github', + message: + 'KEYSTATIC_GITHUB_TOKEN is required when CMS_CLIENT is set to "keystatic"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The KEYSTATIC_GITHUB_TOKEN variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + }, + { + name: 'KEYSTATIC_PATH_PREFIX', + displayName: 'Keystatic Path Prefix', + description: 'Your Keystatic path prefix.', + category: 'CMS', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value) => value === 'keystatic', + message: + 'KEYSTATIC_PATH_PREFIX is required when CMS_CLIENT is set to "keystatic"', + }, + ], + validate: ({ value }) => { + return z.string().safeParse(value); + }, + }, + }, + { + name: 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH', + displayName: 'Keystatic Content Path', + description: 'Your Keystatic content path.', + category: 'CMS', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value) => value === 'keystatic', + message: + 'NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH is required when CMS_CLIENT is set to "keystatic"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + }, + { + name: 'WORDPRESS_API_URL', + displayName: 'WordPress API URL', + description: 'WordPress API URL when using WordPress as CMS.', + category: 'CMS', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'CMS_CLIENT', + condition: (value) => value === 'wordpress', + message: + 'WORDPRESS_API_URL is required when CMS_CLIENT is set to "wordpress"', + }, + ], + validate: ({ value }) => { + return z + .string() + .url({ + message: `The WORDPRESS_API_URL variable must be a valid URL`, + }) + .safeParse(value); + }, + }, + }, + { + name: 'NEXT_PUBLIC_LOCALES_PATH', + displayName: 'Locales Path', + description: 'The path to your locales folder.', + category: 'Localization', + type: 'string', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_LOCALES_PATH variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_LANGUAGE_PRIORITY', + displayName: 'Language Priority', + description: 'The priority setting as to how infer the language.', + category: 'Localization', + type: 'enum', + values: ['user', 'application'], + validate: ({ value }) => { + return z.enum(['user', 'application']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_ENABLE_VERSION_UPDATER', + displayName: 'Enable Version Updater', + description: + 'Enables the version updater to poll the latest version and notify the user.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS', + displayName: 'Version Updater Refetch Interval (seconds)', + description: 'The interval in seconds to check for updates.', + category: 'Features', + type: 'number', + validate: ({ value }) => { + return z.coerce + .number() + .min( + 1, + `The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at least 1 character`, + ) + .max( + 86400, + `The NEXT_PUBLIC_VERSION_UPDATER_REFETCH_INTERVAL_SECONDS variable must be at most 86400`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: `ENABLE_REACT_COMPILER`, + displayName: 'Enable React Compiler', + description: 'Enables the React compiler [experimental]', + category: 'Build', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_MONITORING_PROVIDER', + displayName: 'Monitoring Provider', + description: 'The monitoring provider to use.', + category: 'Monitoring', + type: 'enum', + values: ['baselime', 'sentry', 'none'], + validate: ({ value }) => { + return z.enum(['baselime', 'sentry', '']).optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_SENTRY_DSN', + displayName: 'Sentry DSN', + description: 'The Sentry DSN to use.', + category: 'Monitoring', + type: `string`, + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_MONITORING_PROVIDER', + condition: (value) => value === 'sentry', + message: + 'NEXT_PUBLIC_SENTRY_DSN is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "sentry"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_SENTRY_DSN variable must be at least 1 character`, + ) + .safeParse(value); + }, + }, + }, + { + name: 'NEXT_PUBLIC_SENTRY_ENVIRONMENT', + displayName: 'Sentry Environment', + description: 'The Sentry environment to use.', + category: 'Monitoring', + type: 'string', + validate: ({ value }) => { + return z.string().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_BASELIME_KEY', + displayName: 'Baselime Key', + description: 'The Baselime key to use.', + category: 'Monitoring', + type: 'string', + contextualValidation: { + dependencies: [ + { + variable: 'NEXT_PUBLIC_MONITORING_PROVIDER', + condition: (value) => value === 'baselime', + message: + 'NEXT_PUBLIC_BASELIME_KEY is required when NEXT_PUBLIC_MONITORING_PROVIDER is set to "baselime"', + }, + ], + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_BASELIME_KEY variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + }, + { + name: 'STRIPE_ENABLE_TRIAL_WITHOUT_CC', + displayName: 'Enable Stripe Trial Without Credit Card', + description: 'Enables trial plans without credit card.', + category: 'Billing', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_THEME_COLOR', + displayName: 'Theme Color', + description: 'The default theme color.', + category: 'Theme', + type: 'string', + required: true, + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_THEME_COLOR variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_THEME_COLOR_DARK', + displayName: 'Theme Color (Dark Mode)', + description: 'The default theme color for dark mode.', + category: 'Theme', + required: true, + type: 'string', + validate: ({ value }) => { + return z + .string() + .min( + 1, + `The NEXT_PUBLIC_THEME_COLOR_DARK variable must be at least 1 character`, + ) + .optional() + .safeParse(value); + }, + }, + { + name: 'NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX', + displayName: 'Display Terms and Conditions Checkbox', + description: 'Whether to display the terms checkbox during sign-up.', + category: 'Features', + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, + { + name: 'ENABLE_STRICT_CSP', + displayName: 'Enable Strict Content Security Policy', + description: 'Enables strict Content Security Policy (CSP) headers.', + category: 'Security', + required: false, + type: 'boolean', + validate: ({ value }) => { + return z.coerce.boolean().optional().safeParse(value); + }, + }, +]; diff --git a/packages/mcp-server/src/tools/env/public-api.ts b/packages/mcp-server/src/tools/env/public-api.ts new file mode 100644 index 000000000..0d9f8e080 --- /dev/null +++ b/packages/mcp-server/src/tools/env/public-api.ts @@ -0,0 +1,9 @@ +export { envVariables } from './model'; +export { + findWorkspaceRoot, + getEnvState, + getVariable, + processEnvDefinitions, + scanMonorepoEnv, +} from './scanner'; +export { createKitEnvDeps, createKitEnvService } from './kit-env.service'; diff --git a/packages/mcp-server/src/tools/env/scanner.ts b/packages/mcp-server/src/tools/env/scanner.ts new file mode 100644 index 000000000..671e1d294 --- /dev/null +++ b/packages/mcp-server/src/tools/env/scanner.ts @@ -0,0 +1,480 @@ +import fs from 'fs/promises'; +import { existsSync } from 'node:fs'; +import path from 'path'; + +import { envVariables } from './model'; +import { + AppEnvState, + EnvFileInfo, + EnvMode, + EnvVariableState, + ScanFs, + ScanOptions, +} from './types'; + +// Define precedence order for each mode +const ENV_FILE_PRECEDENCE: Record = { + development: [ + '.env', + '.env.development', + '.env.local', + '.env.development.local', + ], + production: [ + '.env', + '.env.production', + '.env.local', + '.env.production.local', + ], +}; + +function getSourcePrecedence(source: string, mode: EnvMode): number { + return ENV_FILE_PRECEDENCE[mode].indexOf(source); +} + +export async function scanMonorepoEnv( + options: ScanOptions, +): Promise { + const { + rootDir = findWorkspaceRoot(process.cwd()), + apps = ['web'], + mode, + } = options; + + const defaultFs: ScanFs = { + readFile: (filePath) => fs.readFile(filePath, 'utf-8'), + readdir: (dirPath) => fs.readdir(dirPath), + stat: (path) => fs.stat(path), + }; + const fsApi = options.fs ?? defaultFs; + + const envTypes = ENV_FILE_PRECEDENCE[mode]; + const appsDir = path.join(rootDir, 'apps'); + const results: EnvFileInfo[] = []; + + try { + const appDirs = await fsApi.readdir(appsDir); + + for (const appName of appDirs) { + if (apps.length > 0 && !apps.includes(appName)) { + continue; + } + + const appDir = path.join(appsDir, appName); + const stat = await fsApi.stat(appDir); + + if (!stat.isDirectory()) { + continue; + } + + const appInfo: EnvFileInfo = { + appName, + filePath: appDir, + variables: [], + }; + + for (const envType of envTypes) { + const envPath = path.join(appDir, envType); + + try { + const content = await fsApi.readFile(envPath); + const vars = parseEnvFile(content, envType); + + appInfo.variables.push(...vars); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.warn(`Error reading ${envPath}:`, error); + } + } + } + + results.push(appInfo); + } + } catch (error) { + console.error('Error scanning monorepo:', error); + throw error; + } + + return results; +} + +function parseEnvFile(content: string, source: string) { + const variables: Array<{ key: string; value: string; source: string }> = []; + + const lines = content.split('\n'); + + for (const line of lines) { + // Skip comments and empty lines + if (line.trim().startsWith('#') || !line.trim()) { + continue; + } + + // Match KEY=VALUE pattern, handling quotes + const match = line.match(/^([^=]+)=(.*)$/); + if (match) { + const [, key = '', rawValue] = match; + let value = rawValue ?? ''; + + // Remove quotes if present + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + // Handle escaped quotes within the value + value = value + .replace(/\\"/g, '"') + .replace(/\\'/g, "'") + .replace(/\\\\/g, '\\'); + + variables.push({ + key: key.trim(), + value: value.trim(), + source, + }); + } + } + + return variables; +} + +export function processEnvDefinitions( + envInfo: EnvFileInfo, + mode: EnvMode, +): AppEnvState { + const variableMap: Record = {}; + + // First pass: Collect all definitions + for (const variable of envInfo.variables) { + if (!variable) { + continue; + } + + const model = envVariables.find((v) => variable.key === v.name); + + if (!variableMap[variable.key]) { + variableMap[variable.key] = { + key: variable.key, + isVisible: true, + definitions: [], + effectiveValue: variable.value, + effectiveSource: variable.source, + isOverridden: false, + category: model ? model.category : 'Custom', + validation: { + success: true, + error: { + issues: [], + }, + }, + }; + } + + const varState = variableMap[variable.key]; + + if (!varState) { + continue; + } + + varState.definitions.push({ + key: variable.key, + value: variable.value, + source: variable.source, + }); + } + + // Second pass: Determine effective values and override status + for (const key in variableMap) { + const varState = variableMap[key]; + + if (!varState) { + continue; + } + + // Sort definitions by mode-specific precedence + varState.definitions.sort( + (a, b) => + getSourcePrecedence(a.source, mode) - + getSourcePrecedence(b.source, mode), + ); + + if (varState.definitions.length > 1) { + const lastDef = varState.definitions[varState.definitions.length - 1]; + + if (!lastDef) { + continue; + } + + const highestPrecedence = getSourcePrecedence(lastDef.source, mode); + + varState.isOverridden = true; + varState.effectiveValue = lastDef.value; + varState.effectiveSource = lastDef.source; + + // Check for conflicts at highest precedence + const conflictingDefs = varState.definitions.filter( + (def) => getSourcePrecedence(def.source, mode) === highestPrecedence, + ); + + if (conflictingDefs.length > 1) { + varState.effectiveSource = `${varState.effectiveSource} (conflict)`; + } + } + } + + // Build a lookup of all effective values once (used by validations below) + const allVariables: Record = {}; + + for (const key in variableMap) { + const varState = variableMap[key]; + if (varState) { + allVariables[varState.key] = varState.effectiveValue; + } + } + + // after computing the effective values, we can check for errors + for (const key in variableMap) { + const model = envVariables.find((v) => key === v.name); + const varState = variableMap[key]; + + if (!varState) { + continue; + } + + let validation: { + success: boolean; + error: { + issues: string[]; + }; + } = { success: true, error: { issues: [] } }; + + if (model) { + // First check if it's required but missing (use pre-computed allVariables) + if (model.required && !varState.effectiveValue) { + validation = { + success: false, + error: { + issues: [ + `This variable is required but missing from your environment files`, + ], + }, + }; + } else if (model.contextualValidation) { + // Then check contextual validation + const dependenciesMet = model.contextualValidation.dependencies.some( + (dep) => { + const dependencyValue = allVariables[dep.variable] ?? ''; + + return dep.condition(dependencyValue, allVariables); + }, + ); + + if (dependenciesMet) { + // Only check for missing value or run validation if dependencies are met + if (!varState.effectiveValue) { + const dependencyErrors = model.contextualValidation.dependencies + .map((dep) => { + const dependencyValue = allVariables[dep.variable] ?? ''; + + const shouldValidate = dep.condition( + dependencyValue, + allVariables, + ); + + if (shouldValidate) { + const { success } = model.contextualValidation!.validate({ + value: varState.effectiveValue, + variables: allVariables, + mode, + }); + + if (success) { + return null; + } + + return dep.message; + } + + return null; + }) + .filter((message): message is string => message !== null); + + validation = { + success: dependencyErrors.length === 0, + error: { + issues: dependencyErrors + .map((message) => message) + .filter((message) => !!message), + }, + }; + } else { + // If we have a value and dependencies are met, run contextual validation + const result = model.contextualValidation.validate({ + value: varState.effectiveValue, + variables: allVariables, + mode, + }); + + if (!result.success) { + validation = { + success: false, + error: { + issues: result.error.issues + .map((issue) => issue.message) + .filter((message) => !!message), + }, + }; + } + } + } + } else if (model.validate && varState.effectiveValue) { + // Only run regular validation if: + // 1. There's no contextual validation + // 2. There's a value to validate + const result = model.validate({ + value: varState.effectiveValue, + variables: allVariables, + mode, + }); + + if (!result.success) { + validation = { + success: false, + error: { + issues: result.error.issues + .map((issue) => issue.message) + .filter((message) => !!message), + }, + }; + } + } + } + + varState.validation = validation; + } + + // Final pass: Validate missing variables that are marked as required + // or as having contextual validation + for (const model of envVariables) { + // If the variable exists in appState, use that + const existingVar = variableMap[model.name]; + + if (existingVar) { + // If the variable is already in the map, skip it + continue; + } + + if (model.contextualValidation) { + // Check if any dependency condition is met for this missing variable + const errors = model.contextualValidation.dependencies.flatMap((dep) => { + const dependencyValue = allVariables[dep.variable] ?? ''; + const shouldValidate = dep.condition(dependencyValue, allVariables); + + if (!shouldValidate) { + return []; + } + + // Validate with the missing variable's empty value + const validation = model.contextualValidation!.validate({ + value: '', + variables: allVariables, + mode, + }); + + if (!validation.success) { + return [dep.message]; + } + + return []; + }); + + if (errors.length === 0) { + continue; + } + + variableMap[model.name] = { + key: model.name, + effectiveValue: '', + effectiveSource: 'MISSING', + isVisible: true, + category: model.category, + isOverridden: false, + definitions: [], + validation: { + success: false, + error: { + issues: errors, + }, + }, + }; + } else if (model.required) { + // Required but no contextual validation — generic required error + variableMap[model.name] = { + key: model.name, + effectiveValue: '', + effectiveSource: 'MISSING', + isVisible: true, + category: model.category, + isOverridden: false, + definitions: [], + validation: { + success: false, + error: { + issues: [ + `This variable is required but missing from your environment files`, + ], + }, + }, + }; + } + } + + return { + appName: envInfo.appName, + filePath: envInfo.filePath, + mode, + variables: variableMap, + }; +} + +export async function getEnvState( + options: ScanOptions, +): Promise { + const envInfos = await scanMonorepoEnv(options); + return envInfos.map((info) => processEnvDefinitions(info, options.mode)); +} + +export async function getVariable(key: string, mode: EnvMode) { + // Get the processed environment state for all apps (you can limit to 'web' via options) + const envStates = await getEnvState({ mode, apps: ['web'] }); + + // Find the state for the "web" app. + const webState = envStates.find((state) => state.appName === 'web'); + + // Return the effectiveValue based on override status. + return webState?.variables[key]?.effectiveValue ?? ''; +} + +export function findWorkspaceRoot(startPath: string) { + let current = startPath; + + for (let depth = 0; depth < 6; depth++) { + const maybeWorkspace = path.join(current, 'pnpm-workspace.yaml'); + + if (existsSync(maybeWorkspace)) { + return current; + } + + const parent = path.join(current, '..'); + + if (parent === current) { + break; + } + + current = parent; + } + + return startPath; +} diff --git a/packages/mcp-server/src/tools/env/schema.ts b/packages/mcp-server/src/tools/env/schema.ts new file mode 100644 index 000000000..9d9061df9 --- /dev/null +++ b/packages/mcp-server/src/tools/env/schema.ts @@ -0,0 +1,102 @@ +import { z } from 'zod/v3'; + +export const KitEnvModeSchema = z.enum(['development', 'production']); + +export const KitEnvSchemaInputSchema = z.object({}); + +export const KitEnvReadInputSchema = z.object({ + mode: KitEnvModeSchema.default('development'), +}); + +export const KitEnvUpdateInputSchema = z.object({ + key: z.string().min(1), + value: z.string(), + mode: KitEnvModeSchema.optional(), + file: z.string().optional(), +}); + +export const KitEnvRawReadInputSchema = z.object({ + file: z.string().min(1), +}); + +export const KitEnvRawWriteInputSchema = z.object({ + file: z.string().min(1), + content: z.string(), +}); + +export const KitEnvSchemaOutputSchema = z.object({ + groups: z.array( + z.object({ + name: z.string(), + description: z.string(), + variables: z.array( + z.object({ + key: z.string(), + label: z.string(), + description: z.string(), + required: z.boolean(), + type: z.enum(['string', 'url', 'email', 'number', 'boolean', 'enum']), + sensitive: z.boolean(), + values: z.array(z.string()).optional(), + hint: z.string().optional(), + dependencies: z + .array( + z.object({ + variable: z.string(), + condition: z.string(), + }), + ) + .optional(), + }), + ), + }), + ), +}); + +export const KitEnvReadOutputSchema = z.object({ + mode: KitEnvModeSchema, + variables: z.record( + z.object({ + key: z.string(), + value: z.string(), + source: z.string(), + isOverridden: z.boolean(), + overrideChain: z + .array( + z.object({ + source: z.string(), + value: z.string(), + }), + ) + .optional(), + validation: z.object({ + valid: z.boolean(), + errors: z.array(z.string()), + }), + dependencies: z + .array( + z.object({ + variable: z.string(), + condition: z.string(), + satisfied: z.boolean(), + }), + ) + .optional(), + }), + ), +}); + +export const KitEnvUpdateOutputSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); + +export const KitEnvRawReadOutputSchema = z.object({ + content: z.string(), + exists: z.boolean(), +}); + +export const KitEnvRawWriteOutputSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); diff --git a/packages/mcp-server/src/tools/env/types.ts b/packages/mcp-server/src/tools/env/types.ts new file mode 100644 index 000000000..ecbcc4f8d --- /dev/null +++ b/packages/mcp-server/src/tools/env/types.ts @@ -0,0 +1,54 @@ +export type EnvMode = 'development' | 'production'; + +export type ScanFs = { + readFile: (filePath: string) => Promise; + readdir: (dirPath: string) => Promise; + stat: (path: string) => Promise<{ isDirectory(): boolean }>; +}; + +export type ScanOptions = { + apps?: string[]; + rootDir?: string; + mode: EnvMode; + fs?: ScanFs; +}; + +export type EnvDefinition = { + key: string; + value: string; + source: string; +}; + +export type EnvVariableState = { + key: string; + category: string; + definitions: EnvDefinition[]; + effectiveValue: string; + isOverridden: boolean; + effectiveSource: string; + isVisible: boolean; + validation: { + success: boolean; + error: { + issues: string[]; + }; + }; +}; + +export type AppEnvState = { + appName: string; + filePath: string; + mode: EnvMode; + variables: Record; +}; + +export type EnvFileInfo = { + appName: string; + filePath: string; + + variables: Array<{ + key: string; + value: string; + source: string; + }>; +}; diff --git a/packages/mcp-server/src/tools/mailbox/__tests__/kit-mailbox.service.test.ts b/packages/mcp-server/src/tools/mailbox/__tests__/kit-mailbox.service.test.ts new file mode 100644 index 000000000..a531e1a94 --- /dev/null +++ b/packages/mcp-server/src/tools/mailbox/__tests__/kit-mailbox.service.test.ts @@ -0,0 +1,190 @@ +import { describe, expect, it } from 'vitest'; + +import { + type KitMailboxDeps, + createKitMailboxService, +} from '../kit-mailbox.service'; + +function createDeps(overrides: Partial = {}): KitMailboxDeps { + return { + async executeCommand() { + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + }, + async isPortOpen() { + return true; + }, + async fetchJson() { + return {}; + }, + async requestJson() { + return {}; + }, + ...overrides, + }; +} + +describe('KitMailboxService', () => { + it('lists messages from Mailpit API', async () => { + const service = createKitMailboxService( + createDeps({ + async executeCommand() { + return { stdout: 'mailpit\n', stderr: '', exitCode: 0 }; + }, + async fetchJson(url: string) { + if (url.endsWith('/info')) { + return { version: 'v1' }; + } + + return { + total: 1, + unread: 1, + count: 1, + messages: [ + { + ID: 'abc', + MessageID: 'm-1', + Subject: 'Welcome', + From: [{ Name: 'Makerkit', Address: 'noreply@makerkit.dev' }], + To: [{ Address: 'user@example.com' }], + Created: '2025-01-01T00:00:00Z', + Size: 123, + Read: false, + ReadAt: null, + }, + ], + }; + }, + }), + ); + + const result = await service.list({ start: 0, limit: 50 }); + + expect(result.mail_server.running).toBe(true); + expect(result.mail_server.running_via_docker).toBe(true); + expect(result.total).toBe(1); + expect(result.messages[0]).toEqual({ + id: 'abc', + message_id: 'm-1', + subject: 'Welcome', + from: ['Makerkit '], + to: ['user@example.com'], + created_at: '2025-01-01T00:00:00Z', + size: 123, + read: false, + }); + expect(result.messages[0]?.readAt).toBeUndefined(); + }); + + it('reads single message details', async () => { + const service = createKitMailboxService( + createDeps({ + async executeCommand() { + return { stdout: '', stderr: '', exitCode: 0 }; + }, + async fetchJson(url: string) { + if (url.endsWith('/info')) { + return { version: 'v1' }; + } + + if (url.includes('/message/')) { + return { + ID: 'abc', + MessageID: 'm-1', + Subject: 'Welcome', + From: [{ Address: 'noreply@makerkit.dev' }], + To: [{ Address: 'user@example.com' }], + Cc: [{ Address: 'team@example.com' }], + Bcc: [], + Text: ['Hello user'], + HTML: ['

Hello user

'], + Headers: { + Subject: ['Welcome'], + }, + Read: true, + ReadAt: '2025-01-01T00:05:00Z', + Size: 456, + Created: '2025-01-01T00:00:00Z', + }; + } + + return {}; + }, + }), + ); + + const result = await service.read({ id: 'abc' }); + + expect(result.id).toBe('abc'); + expect(result.subject).toBe('Welcome'); + expect(result.from).toEqual(['noreply@makerkit.dev']); + expect(result.to).toEqual(['user@example.com']); + expect(result.cc).toEqual(['team@example.com']); + expect(result.text).toBe('Hello user'); + expect(result.html).toBe('

Hello user

'); + expect(result.read).toBe(true); + expect(result.readAt).toBe('2025-01-01T00:05:00Z'); + expect(result.headers).toEqual({ Subject: ['Welcome'] }); + }); + + it('updates read status for a message', async () => { + const service = createKitMailboxService( + createDeps({ + async requestJson(url: string) { + expect(url).toContain('/message/abc/read'); + + return { + id: 'abc', + read: true, + readAt: '2025-01-01T00:10:00Z', + }; + }, + }), + ); + + const result = await service.setReadStatus({ id: 'abc', read: true }); + + expect(result).toEqual({ + id: 'abc', + read: true, + readAt: '2025-01-01T00:10:00Z', + }); + }); + + it('throws if mailpit runs in docker but API port is not open', async () => { + const service = createKitMailboxService( + createDeps({ + async isPortOpen() { + return false; + }, + async executeCommand() { + return { stdout: 'mailpit\n', stderr: '', exitCode: 0 }; + }, + }), + ); + + await expect(service.list({ start: 0, limit: 50 })).rejects.toThrow( + 'Mailpit appears running in docker but API is unreachable at http://127.0.0.1:8025', + ); + }); + + it('throws if mailpit is not running', async () => { + const service = createKitMailboxService( + createDeps({ + async isPortOpen() { + return false; + }, + async executeCommand() { + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + await expect(service.list({ start: 0, limit: 50 })).rejects.toThrow( + 'Mailpit is not running. Start local services with "pnpm compose:dev:up".', + ); + }); +}); diff --git a/packages/mcp-server/src/tools/mailbox/index.ts b/packages/mcp-server/src/tools/mailbox/index.ts new file mode 100644 index 000000000..be0c49bcf --- /dev/null +++ b/packages/mcp-server/src/tools/mailbox/index.ts @@ -0,0 +1,194 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { Socket } from 'node:net'; +import { promisify } from 'node:util'; + +import { + type KitMailboxDeps, + createKitMailboxService, +} from './kit-mailbox.service'; +import { + KitEmailsListInputSchema, + KitEmailsListOutputSchema, + KitEmailsReadInputSchema, + KitEmailsReadOutputSchema, + KitEmailsSetReadStatusInputSchema, + KitEmailsSetReadStatusOutputSchema, +} from './schema'; + +const execFileAsync = promisify(execFile); + +type TextContent = { + type: 'text'; + text: string; +}; + +export function registerKitEmailsTools(server: McpServer) { + const service = createKitMailboxService(createKitMailboxDeps()); + + server.registerTool( + 'kit_emails_list', + { + description: + 'List received emails from the local Mailpit inbox (runtime mailbox, not source templates)', + inputSchema: KitEmailsListInputSchema, + outputSchema: KitEmailsListOutputSchema, + }, + async (input) => { + try { + const parsed = KitEmailsListInputSchema.parse(input); + const result = await service.list(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_emails_list', error); + } + }, + ); + + server.registerTool( + 'kit_emails_read', + { + description: + 'Read a received email from the local Mailpit inbox by message id (includes text/html/headers)', + inputSchema: KitEmailsReadInputSchema, + outputSchema: KitEmailsReadOutputSchema, + }, + async (input) => { + try { + const parsed = KitEmailsReadInputSchema.parse(input); + const result = await service.read(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_emails_read', error); + } + }, + ); + + server.registerTool( + 'kit_emails_set_read_status', + { + description: + 'Set read/unread status for a received email in the local Mailpit inbox', + inputSchema: KitEmailsSetReadStatusInputSchema, + outputSchema: KitEmailsSetReadStatusOutputSchema, + }, + async (input) => { + try { + const parsed = KitEmailsSetReadStatusInputSchema.parse(input); + const result = await service.setReadStatus(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_emails_set_read_status', error); + } + }, + ); +} + +export function createKitMailboxDeps(rootPath = process.cwd()): KitMailboxDeps { + return { + async executeCommand(command: string, args: string[]) { + const result = await execFileAsync(command, args, { cwd: rootPath }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + async isPortOpen(port: number) { + return checkPort(port); + }, + async fetchJson(url: string) { + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Mailpit API request failed with status ${response.status}`, + ); + } + + return response.json(); + }, + async requestJson(url: string, init) { + const response = await fetch(url, { + method: init?.method ?? 'GET', + headers: init?.headers, + body: init?.body, + }); + + if (!response.ok) { + throw new Error( + `Mailpit API request failed with status ${response.status}`, + ); + } + + return response.json(); + }, + }; +} + +async function checkPort(port: number) { + return new Promise((resolve) => { + const socket = new Socket(); + + socket.setTimeout(200); + + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.once('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.once('error', () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, '127.0.0.1'); + }); +} + +function buildErrorResponse(tool: string, error: unknown) { + const message = `${tool} failed: ${toErrorMessage(error)}`; + + return { + isError: true, + content: buildTextContent(message), + }; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +function buildTextContent(text: string): TextContent[] { + return [{ type: 'text', text }]; +} + +export { createKitMailboxService } from './kit-mailbox.service'; +export type { KitMailboxDeps } from './kit-mailbox.service'; +export type { + KitEmailsListOutput, + KitEmailsReadOutput, + KitEmailsSetReadStatusOutput, +} from './schema'; diff --git a/packages/mcp-server/src/tools/mailbox/kit-mailbox.service.ts b/packages/mcp-server/src/tools/mailbox/kit-mailbox.service.ts new file mode 100644 index 000000000..28f241dd8 --- /dev/null +++ b/packages/mcp-server/src/tools/mailbox/kit-mailbox.service.ts @@ -0,0 +1,261 @@ +import type { + KitEmailsListInput, + KitEmailsListOutput, + KitEmailsReadInput, + KitEmailsReadOutput, + KitEmailsSetReadStatusInput, + KitEmailsSetReadStatusOutput, +} from './schema'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +export interface KitMailboxDeps { + executeCommand(command: string, args: string[]): Promise; + isPortOpen(port: number): Promise; + fetchJson(url: string): Promise; + requestJson( + url: string, + init?: { + method?: string; + body?: string; + headers?: Record; + }, + ): Promise; +} + +interface MailServerStatus { + running: boolean; + running_via_docker: boolean; + api_base_url: string; +} + +const MAILPIT_HTTP_PORT = 54324; +const MAILPIT_API_BASE_URL = 'http://127.0.0.1:54324/api/v1'; + +export function createKitMailboxService(deps: KitMailboxDeps) { + return new KitMailboxService(deps); +} + +export class KitMailboxService { + constructor(private readonly deps: KitMailboxDeps) {} + + async list(input: KitEmailsListInput): Promise { + const mailServer = await this.ensureMailServerReady(); + + const payload = asRecord( + await this.deps.fetchJson( + `${MAILPIT_API_BASE_URL}/messages?start=${input.start}&limit=${input.limit}`, + ), + ); + + const messages = asArray(payload.messages ?? payload.Messages).map( + (message) => this.toSummary(asRecord(message)), + ); + + return { + mail_server: mailServer, + start: toNumber(payload.start ?? payload.Start) ?? input.start, + limit: toNumber(payload.limit ?? payload.Limit) ?? input.limit, + count: toNumber(payload.count ?? payload.Count) ?? messages.length, + total: toNumber(payload.total ?? payload.Total) ?? messages.length, + unread: toNumber(payload.unread ?? payload.Unread), + messages, + }; + } + + async read(input: KitEmailsReadInput): Promise { + const mailServer = await this.ensureMailServerReady(); + + const message = asRecord( + await this.deps.fetchJson( + `${MAILPIT_API_BASE_URL}/message/${encodeURIComponent(input.id)}`, + ), + ); + + return { + mail_server: mailServer, + id: toString(message.ID ?? message.id) ?? input.id, + message_id: toString(message.MessageID ?? message.messageId), + subject: toString(message.Subject ?? message.subject), + from: readAddressList(message.From ?? message.from), + to: readAddressList(message.To ?? message.to), + cc: readAddressList(message.Cc ?? message.cc), + bcc: readAddressList(message.Bcc ?? message.bcc), + created_at: toString(message.Created ?? message.created), + size: toNumber(message.Size ?? message.size), + read: toBoolean(message.Read ?? message.read) ?? false, + readAt: toString(message.ReadAt ?? message.readAt), + text: readBody(message.Text ?? message.text), + html: readBody(message.HTML ?? message.Html ?? message.html), + headers: readHeaders(message.Headers ?? message.headers), + raw: message, + }; + } + + async setReadStatus( + input: KitEmailsSetReadStatusInput, + ): Promise { + await this.ensureMailServerReady(); + + const response = asRecord( + await this.deps.requestJson( + `${MAILPIT_API_BASE_URL}/message/${encodeURIComponent(input.id)}/read`, + { + method: 'PUT', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ read: input.read }), + }, + ), + ); + + const read = toBoolean(response.Read ?? response.read) ?? input.read; + const readAt = toString(response.ReadAt ?? response.readAt); + + return { + id: toString(response.ID ?? response.id) ?? input.id, + read, + ...(readAt ? { readAt } : {}), + }; + } + + private toSummary(message: Record) { + const readAt = toString(message.ReadAt ?? message.readAt); + + return { + id: toString(message.ID ?? message.id) ?? '', + message_id: toString(message.MessageID ?? message.messageId), + subject: toString(message.Subject ?? message.subject), + from: readAddressList(message.From ?? message.from), + to: readAddressList(message.To ?? message.to), + created_at: toString(message.Created ?? message.created), + size: toNumber(message.Size ?? message.size), + read: toBoolean(message.Read ?? message.read) ?? false, + ...(readAt ? { readAt } : {}), + }; + } + + private async ensureMailServerReady(): Promise { + const running = await this.deps.isPortOpen(MAILPIT_HTTP_PORT); + const runningViaDocker = await this.isMailpitRunningViaDocker(); + + if (!running) { + if (runningViaDocker) { + throw new Error( + 'Mailpit appears running in docker but API is unreachable at http://127.0.0.1:8025', + ); + } + + throw new Error( + 'Mailpit is not running. Start local services with "pnpm compose:dev:up".', + ); + } + + await this.deps.fetchJson(`${MAILPIT_API_BASE_URL}/info`); + + return { + running, + running_via_docker: runningViaDocker, + api_base_url: MAILPIT_API_BASE_URL, + }; + } + + private async isMailpitRunningViaDocker() { + try { + const result = await this.deps.executeCommand('docker', [ + 'compose', + '-f', + 'docker-compose.dev.yml', + 'ps', + '--status', + 'running', + '--services', + ]); + + const services = result.stdout + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); + + return services.includes('mailpit'); + } catch { + return false; + } + } +} + +function readHeaders(input: unknown) { + const headers = asRecord(input); + const normalized: Record = {}; + + for (const [key, value] of Object.entries(headers)) { + normalized[key] = asArray(value) + .map((item) => toString(item)) + .filter((item): item is string => item !== null); + } + + return normalized; +} + +function readBody(input: unknown): string | null { + if (typeof input === 'string') { + return input; + } + + if (Array.isArray(input)) { + const chunks = input + .map((item) => toString(item)) + .filter((item): item is string => item !== null); + + return chunks.length > 0 ? chunks.join('\n\n') : null; + } + + return null; +} + +function readAddressList(input: unknown): string[] { + return asArray(input) + .map((entry) => { + const item = asRecord(entry); + const address = + toString(item.Address ?? item.address ?? item.Email ?? item.email) ?? + ''; + const name = toString(item.Name ?? item.name); + + if (!address) { + return null; + } + + return name ? `${name} <${address}>` : address; + }) + .filter((value): value is string => value !== null); +} + +function asRecord(value: unknown): Record { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + + return value as Record; +} + +function asArray(value: unknown): unknown[] { + return Array.isArray(value) ? value : []; +} + +function toString(value: unknown): string | null { + return typeof value === 'string' ? value : null; +} + +function toBoolean(value: unknown): boolean | null { + return typeof value === 'boolean' ? value : null; +} + +function toNumber(value: unknown): number | null { + return typeof value === 'number' && Number.isFinite(value) ? value : null; +} diff --git a/packages/mcp-server/src/tools/mailbox/schema.ts b/packages/mcp-server/src/tools/mailbox/schema.ts new file mode 100644 index 000000000..82c8a2676 --- /dev/null +++ b/packages/mcp-server/src/tools/mailbox/schema.ts @@ -0,0 +1,79 @@ +import { z } from 'zod/v3'; + +export const KitEmailsListInputSchema = z.object({ + start: z.number().int().min(0).default(0), + limit: z.number().int().min(1).max(200).default(50), +}); + +const MailServerStatusSchema = z.object({ + running: z.boolean(), + running_via_docker: z.boolean(), + api_base_url: z.string(), +}); + +const EmailSummarySchema = z.object({ + id: z.string(), + message_id: z.string().nullable(), + subject: z.string().nullable(), + from: z.array(z.string()), + to: z.array(z.string()), + created_at: z.string().nullable(), + size: z.number().nullable(), + read: z.boolean(), + readAt: z.string().optional(), +}); + +export const KitEmailsListOutputSchema = z.object({ + mail_server: MailServerStatusSchema, + start: z.number(), + limit: z.number(), + count: z.number(), + total: z.number(), + unread: z.number().nullable(), + messages: z.array(EmailSummarySchema), +}); + +export const KitEmailsReadInputSchema = z.object({ + id: z.string().min(1), +}); + +export const KitEmailsReadOutputSchema = z.object({ + mail_server: MailServerStatusSchema, + id: z.string(), + message_id: z.string().nullable(), + subject: z.string().nullable(), + from: z.array(z.string()), + to: z.array(z.string()), + cc: z.array(z.string()), + bcc: z.array(z.string()), + created_at: z.string().nullable(), + size: z.number().nullable(), + read: z.boolean(), + readAt: z.string().optional(), + text: z.string().nullable(), + html: z.string().nullable(), + headers: z.record(z.array(z.string())), + raw: z.unknown(), +}); + +export const KitEmailsSetReadStatusInputSchema = z.object({ + id: z.string().min(1), + read: z.boolean(), +}); + +export const KitEmailsSetReadStatusOutputSchema = z.object({ + id: z.string(), + read: z.boolean(), + readAt: z.string().optional(), +}); + +export type KitEmailsListInput = z.infer; +export type KitEmailsListOutput = z.infer; +export type KitEmailsReadInput = z.infer; +export type KitEmailsReadOutput = z.infer; +export type KitEmailsSetReadStatusInput = z.infer< + typeof KitEmailsSetReadStatusInputSchema +>; +export type KitEmailsSetReadStatusOutput = z.infer< + typeof KitEmailsSetReadStatusOutputSchema +>; diff --git a/packages/mcp-server/src/tools/migrations.ts b/packages/mcp-server/src/tools/migrations.ts index 17de7bee7..5e1395a3b 100644 --- a/packages/mcp-server/src/tools/migrations.ts +++ b/packages/mcp-server/src/tools/migrations.ts @@ -2,7 +2,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { execSync } from 'node:child_process'; import { readFile, readdir } from 'node:fs/promises'; import { join } from 'node:path'; -import { z } from 'zod'; +import { z } from 'zod/v3'; export class MigrationsTool { static GetMigrations() { @@ -35,9 +35,12 @@ export function registerGetMigrationsTools(server: McpServer) { } function createDiffMigrationTool(server: McpServer) { - return server.tool( + return server.registerTool( 'diff_migrations', - 'Compare differences between the declarative schemas and the applied migrations in Supabase', + { + description: + 'Compare differences between the declarative schemas and the applied migrations in Supabase', + }, async () => { const result = MigrationsTool.Diff(); const text = result.toString('utf8'); @@ -55,13 +58,15 @@ function createDiffMigrationTool(server: McpServer) { } function createCreateMigrationTool(server: McpServer) { - return server.tool( + return server.registerTool( 'create_migration', - 'Create a new Supabase Postgres migration file', { - state: z.object({ - name: z.string(), - }), + description: 'Create a new Supabase Postgres migration file', + inputSchema: { + state: z.object({ + name: z.string(), + }), + }, }, async ({ state }) => { const result = MigrationsTool.CreateMigration(state.name); @@ -80,13 +85,16 @@ function createCreateMigrationTool(server: McpServer) { } function createGetMigrationContentTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_migration_content', - '📜 Get migration file content (HISTORICAL) - For current state use get_schema_content instead', { - state: z.object({ - path: z.string(), - }), + description: + '📜 Get migration file content (HISTORICAL) - For current state use get_schema_content instead', + inputSchema: { + state: z.object({ + path: z.string(), + }), + }, }, async ({ state }) => { const content = await MigrationsTool.getMigrationContent(state.path); @@ -104,9 +112,12 @@ function createGetMigrationContentTool(server: McpServer) { } function createGetMigrationsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_migrations', - '📜 Get migration files (HISTORICAL CHANGES) - Use schema files for current state instead', + { + description: + '📜 Get migration files (HISTORICAL CHANGES) - Use schema files for current state instead', + }, async () => { const migrations = await MigrationsTool.GetMigrations(); diff --git a/packages/mcp-server/src/tools/prd-manager.ts b/packages/mcp-server/src/tools/prd-manager.ts index 1e698949d..5513032e9 100644 --- a/packages/mcp-server/src/tools/prd-manager.ts +++ b/packages/mcp-server/src/tools/prd-manager.ts @@ -1,7 +1,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'; +import { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { z } from 'zod'; +import { z } from 'zod/v3'; // Custom phase for organizing user stories interface CustomPhase { @@ -34,6 +34,56 @@ interface UserStory { completedAt?: string; } +interface RiskItem { + id: string; + description: string; + mitigation: string; + owner: string; + severity: 'low' | 'medium' | 'high'; +} + +interface CrossDependency { + id: string; + name: string; + description: string; + blocking: boolean; + owner?: string; +} + +interface DecisionLogEntry { + id: string; + date: string; + decision: string; + rationale: string; + owner?: string; + status: 'proposed' | 'accepted' | 'superseded'; +} + +interface AgentTaskPacket { + id: string; + title: string; + scope: string; + doneCriteria: string[]; + testPlan: string[]; + likelyFiles: string[]; + linkedStoryIds: string[]; + dependencies: string[]; +} + +interface StoryTraceabilityMap { + storyId: string; + featureId: string; + acceptanceCriteriaIds: string[]; + successMetricIds: string[]; +} + +interface CreateStructuredPRDOptions { + nonGoals?: string[]; + outOfScope?: string[]; + assumptions?: string[]; + openQuestions?: string[]; +} + // Structured PRD following ChatPRD format interface StructuredPRD { introduction: { @@ -54,8 +104,16 @@ interface StructuredPRD { successMetrics: string[]; }; + nonGoals: string[]; + outOfScope: string[]; + assumptions: string[]; + openQuestions: string[]; + risks: RiskItem[]; + dependencies: CrossDependency[]; + userStories: UserStory[]; customPhases?: CustomPhase[]; + storyTraceability: StoryTraceabilityMap[]; technicalRequirements: { constraints: string[]; @@ -63,6 +121,13 @@ interface StructuredPRD { complianceRequirements: string[]; }; + technicalContracts: { + apis: string[]; + dataModels: string[]; + permissions: string[]; + integrationBoundaries: string[]; + }; + acceptanceCriteria: { global: string[]; qualityStandards: string[]; @@ -75,10 +140,30 @@ interface StructuredPRD { nonNegotiables: string[]; }; + rolloutPlan: { + featureFlags: string[]; + migrationPlan: string[]; + rolloutPhases: string[]; + rollbackConditions: string[]; + }; + + measurementPlan: { + events: string[]; + dashboards: string[]; + baselineMetrics: string[]; + targetMetrics: string[]; + guardrailMetrics: string[]; + }; + + decisionLog: DecisionLogEntry[]; + agentTaskPackets: AgentTaskPacket[]; + changeLog: string[]; + metadata: { version: string; created: string; lastUpdated: string; + lastValidatedAt: string; approver: string; }; @@ -118,6 +203,7 @@ export class PRDManager { solutionDescription: string, keyFeatures: string[], successMetrics: string[], + options?: CreateStructuredPRDOptions, ): Promise { await this.ensurePRDsDirectory(); @@ -140,12 +226,25 @@ export class PRDManager { keyFeatures, successMetrics, }, + nonGoals: options?.nonGoals ?? [], + outOfScope: options?.outOfScope ?? [], + assumptions: options?.assumptions ?? [], + openQuestions: options?.openQuestions ?? [], + risks: [], + dependencies: [], userStories: [], + storyTraceability: [], technicalRequirements: { constraints: [], integrationNeeds: [], complianceRequirements: [], }, + technicalContracts: { + apis: [], + dataModels: [], + permissions: [], + integrationBoundaries: [], + }, acceptanceCriteria: { global: [], qualityStandards: [], @@ -155,10 +254,27 @@ export class PRDManager { resources: [], nonNegotiables: [], }, + rolloutPlan: { + featureFlags: [], + migrationPlan: [], + rolloutPhases: [], + rollbackConditions: [], + }, + measurementPlan: { + events: [], + dashboards: [], + baselineMetrics: [], + targetMetrics: [], + guardrailMetrics: [], + }, + decisionLog: [], + agentTaskPackets: [], + changeLog: ['Initial PRD created'], metadata: { - version: '1.0', + version: '2.0', created: now, lastUpdated: now, + lastValidatedAt: now, approver: '', }, progress: { @@ -294,6 +410,28 @@ export class PRDManager { suggestions.push('Add global acceptance criteria for quality standards'); } + if (prd.nonGoals.length === 0 || prd.outOfScope.length === 0) { + suggestions.push( + 'Define both non-goals and out-of-scope items to reduce implementation drift', + ); + } + + if (prd.openQuestions.length > 0) { + suggestions.push( + `${prd.openQuestions.length} open questions remain unresolved`, + ); + } + + if (prd.measurementPlan.targetMetrics.length === 0) { + suggestions.push( + 'Define target metrics in measurementPlan to validate delivery impact', + ); + } + + if (prd.rolloutPlan.rolloutPhases.length === 0) { + suggestions.push('Add rollout phases and rollback conditions'); + } + const vagueStories = prd.userStories.filter( (s) => s.acceptanceCriteria.length < 2, ); @@ -336,11 +474,24 @@ export class PRDManager { } } + static async deletePRD(filename: string): Promise { + const filePath = join(this.PRDS_DIR, filename); + + try { + await unlink(filePath); + return `PRD deleted successfully: ${filename}`; + } catch { + throw new Error(`PRD file "${filename}" not found`); + } + } + static async getProjectStatus(filename: string): Promise<{ progress: number; summary: string; nextSteps: string[]; blockers: UserStory[]; + openQuestions: string[]; + highRisks: RiskItem[]; }> { const prd = await this.loadPRD(filename); @@ -357,13 +508,16 @@ export class PRDManager { ...nextPending.map((s) => `Start: ${s.title}`), ]; - const summary = `${prd.progress.completed}/${prd.progress.total} stories completed (${prd.progress.overall}%). Total stories: ${prd.userStories.length}`; + const highRisks = prd.risks.filter((risk) => risk.severity === 'high'); + const summary = `${prd.progress.completed}/${prd.progress.total} stories completed (${prd.progress.overall}%). Total stories: ${prd.userStories.length}. Open questions: ${prd.openQuestions.length}. High risks: ${highRisks.length}.`; return { progress: prd.progress.overall, summary, nextSteps, blockers, + openQuestions: prd.openQuestions, + highRisks, }; } @@ -526,7 +680,7 @@ export class PRDManager { const filePath = join(this.PRDS_DIR, filename); try { const content = await readFile(filePath, 'utf8'); - return JSON.parse(content); + return this.normalizePRD(JSON.parse(content)); } catch { throw new Error(`PRD file "${filename}" not found`); } @@ -536,11 +690,101 @@ export class PRDManager { filename: string, prd: StructuredPRD, ): Promise { - prd.metadata.lastUpdated = new Date().toISOString().split('T')[0]; + const now = new Date().toISOString().split('T')[0]; + prd.metadata.lastUpdated = now; + prd.metadata.lastValidatedAt = prd.metadata.lastValidatedAt || now; + if (prd.changeLog.length === 0) { + prd.changeLog.push(`Updated on ${now}`); + } + const filePath = join(this.PRDS_DIR, filename); await writeFile(filePath, JSON.stringify(prd, null, 2), 'utf8'); } + private static normalizePRD(input: unknown): StructuredPRD { + const prd = input as Partial; + const today = new Date().toISOString().split('T')[0]; + + return { + introduction: { + title: prd.introduction?.title ?? 'Untitled PRD', + overview: prd.introduction?.overview ?? '', + lastUpdated: prd.introduction?.lastUpdated ?? today, + }, + problemStatement: { + problem: prd.problemStatement?.problem ?? '', + marketOpportunity: prd.problemStatement?.marketOpportunity ?? '', + targetUsers: prd.problemStatement?.targetUsers ?? [], + }, + solutionOverview: { + description: prd.solutionOverview?.description ?? '', + keyFeatures: prd.solutionOverview?.keyFeatures ?? [], + successMetrics: prd.solutionOverview?.successMetrics ?? [], + }, + nonGoals: prd.nonGoals ?? [], + outOfScope: prd.outOfScope ?? [], + assumptions: prd.assumptions ?? [], + openQuestions: prd.openQuestions ?? [], + risks: prd.risks ?? [], + dependencies: prd.dependencies ?? [], + userStories: prd.userStories ?? [], + customPhases: prd.customPhases ?? [], + storyTraceability: prd.storyTraceability ?? [], + technicalRequirements: { + constraints: prd.technicalRequirements?.constraints ?? [], + integrationNeeds: prd.technicalRequirements?.integrationNeeds ?? [], + complianceRequirements: + prd.technicalRequirements?.complianceRequirements ?? [], + }, + technicalContracts: { + apis: prd.technicalContracts?.apis ?? [], + dataModels: prd.technicalContracts?.dataModels ?? [], + permissions: prd.technicalContracts?.permissions ?? [], + integrationBoundaries: + prd.technicalContracts?.integrationBoundaries ?? [], + }, + acceptanceCriteria: { + global: prd.acceptanceCriteria?.global ?? [], + qualityStandards: prd.acceptanceCriteria?.qualityStandards ?? [], + }, + constraints: { + timeline: prd.constraints?.timeline ?? '', + budget: prd.constraints?.budget, + resources: prd.constraints?.resources ?? [], + nonNegotiables: prd.constraints?.nonNegotiables ?? [], + }, + rolloutPlan: { + featureFlags: prd.rolloutPlan?.featureFlags ?? [], + migrationPlan: prd.rolloutPlan?.migrationPlan ?? [], + rolloutPhases: prd.rolloutPlan?.rolloutPhases ?? [], + rollbackConditions: prd.rolloutPlan?.rollbackConditions ?? [], + }, + measurementPlan: { + events: prd.measurementPlan?.events ?? [], + dashboards: prd.measurementPlan?.dashboards ?? [], + baselineMetrics: prd.measurementPlan?.baselineMetrics ?? [], + targetMetrics: prd.measurementPlan?.targetMetrics ?? [], + guardrailMetrics: prd.measurementPlan?.guardrailMetrics ?? [], + }, + decisionLog: prd.decisionLog ?? [], + agentTaskPackets: prd.agentTaskPackets ?? [], + changeLog: prd.changeLog ?? [], + metadata: { + version: prd.metadata?.version ?? '2.0', + created: prd.metadata?.created ?? today, + lastUpdated: prd.metadata?.lastUpdated ?? today, + lastValidatedAt: prd.metadata?.lastValidatedAt ?? today, + approver: prd.metadata?.approver ?? '', + }, + progress: { + overall: prd.progress?.overall ?? 0, + completed: prd.progress?.completed ?? 0, + total: prd.progress?.total ?? 0, + blocked: prd.progress?.blocked ?? 0, + }, + }; + } + private static extractTitleFromAction(action: string): string { const cleaned = action.trim().toLowerCase(); const words = cleaned.split(' ').slice(0, 4); @@ -604,6 +848,58 @@ export class PRDManager { content += `- ${metric}\n`; }); + content += `\n## Scope Guardrails\n\n`; + content += `### Non-Goals\n`; + if (prd.nonGoals.length > 0) { + prd.nonGoals.forEach((item) => { + content += `- ${item}\n`; + }); + } else { + content += `- None specified\n`; + } + + content += `\n### Out of Scope\n`; + if (prd.outOfScope.length > 0) { + prd.outOfScope.forEach((item) => { + content += `- ${item}\n`; + }); + } else { + content += `- None specified\n`; + } + + content += `\n### Assumptions\n`; + if (prd.assumptions.length > 0) { + prd.assumptions.forEach((item) => { + content += `- ${item}\n`; + }); + } else { + content += `- None specified\n`; + } + + content += `\n### Open Questions\n`; + if (prd.openQuestions.length > 0) { + prd.openQuestions.forEach((item) => { + content += `- ${item}\n`; + }); + } else { + content += `- None\n`; + } + + if (prd.risks.length > 0) { + content += `\n## Risks\n`; + prd.risks.forEach((risk) => { + content += `- [${risk.severity}] ${risk.description} | Mitigation: ${risk.mitigation} | Owner: ${risk.owner}\n`; + }); + } + + if (prd.dependencies.length > 0) { + content += `\n## Dependencies\n`; + prd.dependencies.forEach((dependency) => { + const mode = dependency.blocking ? 'blocking' : 'non-blocking'; + content += `- ${dependency.name} (${mode}) - ${dependency.description}\n`; + }); + } + content += `\n## User Stories\n\n`; const priorities: UserStory['priority'][] = ['P0', 'P1', 'P2', 'P3']; @@ -637,6 +933,20 @@ export class PRDManager { content += `**Blocked:** ${prd.progress.blocked} stories need attention\n`; } + if (prd.rolloutPlan.rolloutPhases.length > 0) { + content += `\n## Rollout Plan\n`; + prd.rolloutPlan.rolloutPhases.forEach((phase) => { + content += `- ${phase}\n`; + }); + } + + if (prd.measurementPlan.targetMetrics.length > 0) { + content += `\n## Measurement Plan\n`; + prd.measurementPlan.targetMetrics.forEach((metric) => { + content += `- ${metric}\n`; + }); + } + content += `\n---\n\n`; content += `*Approver: ${prd.metadata.approver || 'TBD'}*\n`; @@ -661,6 +971,7 @@ export function registerPRDTools(server: McpServer) { createListPRDsTool(server); createGetPRDTool(server); createCreatePRDTool(server); + createDeletePRDTool(server); createAddUserStoryTool(server); createUpdateStoryStatusTool(server); createExportMarkdownTool(server); @@ -670,9 +981,11 @@ export function registerPRDTools(server: McpServer) { } function createListPRDsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'list_prds', - 'List all Product Requirements Documents', + { + description: 'List all Product Requirements Documents', + }, async () => { const prds = await PRDManager.listPRDs(); @@ -702,13 +1015,15 @@ function createListPRDsTool(server: McpServer) { } function createGetPRDTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_prd', - 'Get the contents of a specific PRD file', { - state: z.object({ - filename: z.string(), - }), + description: 'Get the contents of a specific PRD file', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, }, async ({ state }) => { const content = await PRDManager.getPRDContent(state.filename); @@ -726,20 +1041,27 @@ function createGetPRDTool(server: McpServer) { } function createCreatePRDTool(server: McpServer) { - return server.tool( + return server.registerTool( 'create_prd', - 'Create a new structured PRD following ChatPRD best practices', { - state: z.object({ - title: z.string(), - overview: z.string(), - problemStatement: z.string(), - marketOpportunity: z.string(), - targetUsers: z.array(z.string()), - solutionDescription: z.string(), - keyFeatures: z.array(z.string()), - successMetrics: z.array(z.string()), - }), + description: + 'Create a new structured PRD following ChatPRD best practices', + inputSchema: { + state: z.object({ + title: z.string(), + overview: z.string(), + problemStatement: z.string(), + marketOpportunity: z.string(), + targetUsers: z.array(z.string()), + solutionDescription: z.string(), + keyFeatures: z.array(z.string()), + successMetrics: z.array(z.string()), + nonGoals: z.array(z.string()).optional(), + outOfScope: z.array(z.string()).optional(), + assumptions: z.array(z.string()).optional(), + openQuestions: z.array(z.string()).optional(), + }), + }, }, async ({ state }) => { const filename = await PRDManager.createStructuredPRD( @@ -751,6 +1073,12 @@ function createCreatePRDTool(server: McpServer) { state.solutionDescription, state.keyFeatures, state.successMetrics, + { + nonGoals: state.nonGoals, + outOfScope: state.outOfScope, + assumptions: state.assumptions, + openQuestions: state.openQuestions, + }, ); return { @@ -765,19 +1093,47 @@ function createCreatePRDTool(server: McpServer) { ); } -function createAddUserStoryTool(server: McpServer) { - return server.tool( - 'add_user_story', - 'Add a new user story to an existing PRD', +function createDeletePRDTool(server: McpServer) { + return server.registerTool( + 'delete_prd', { - state: z.object({ - filename: z.string(), - userType: z.string(), - action: z.string(), - benefit: z.string(), - acceptanceCriteria: z.array(z.string()), - priority: z.enum(['P0', 'P1', 'P2', 'P3']).default('P2'), - }), + description: 'Delete an existing PRD file', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, + }, + async ({ state }) => { + const result = await PRDManager.deletePRD(state.filename); + + return { + content: [ + { + type: 'text', + text: result, + }, + ], + }; + }, + ); +} + +function createAddUserStoryTool(server: McpServer) { + return server.registerTool( + 'add_user_story', + { + description: 'Add a new user story to an existing PRD', + inputSchema: { + state: z.object({ + filename: z.string(), + userType: z.string(), + action: z.string(), + benefit: z.string(), + acceptanceCriteria: z.array(z.string()), + priority: z.enum(['P0', 'P1', 'P2', 'P3']).default('P2'), + }), + }, }, async ({ state }) => { const result = await PRDManager.addUserStory( @@ -802,23 +1158,25 @@ function createAddUserStoryTool(server: McpServer) { } function createUpdateStoryStatusTool(server: McpServer) { - return server.tool( + return server.registerTool( 'update_story_status', - 'Update the status of a specific user story', { - state: z.object({ - filename: z.string(), - storyId: z.string(), - status: z.enum([ - 'not_started', - 'research', - 'in_progress', - 'review', - 'completed', - 'blocked', - ]), - notes: z.string().optional(), - }), + description: 'Update the status of a specific user story', + inputSchema: { + state: z.object({ + filename: z.string(), + storyId: z.string(), + status: z.enum([ + 'not_started', + 'research', + 'in_progress', + 'review', + 'completed', + 'blocked', + ]), + notes: z.string().optional(), + }), + }, }, async ({ state }) => { const result = await PRDManager.updateStoryStatus( @@ -841,13 +1199,15 @@ function createUpdateStoryStatusTool(server: McpServer) { } function createExportMarkdownTool(server: McpServer) { - return server.tool( + return server.registerTool( 'export_prd_markdown', - 'Export PRD as markdown for visualization and sharing', { - state: z.object({ - filename: z.string(), - }), + description: 'Export PRD as markdown for visualization and sharing', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, }, async ({ state }) => { const markdownFile = await PRDManager.exportAsMarkdown(state.filename); @@ -865,13 +1225,15 @@ function createExportMarkdownTool(server: McpServer) { } function createGetImplementationPromptsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_implementation_prompts', - 'Generate Claude Code implementation prompts from PRD', { - state: z.object({ - filename: z.string(), - }), + description: 'Generate Claude Code implementation prompts from PRD', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, }, async ({ state }) => { const prompts = await PRDManager.generateImplementationPrompts( @@ -904,13 +1266,15 @@ function createGetImplementationPromptsTool(server: McpServer) { } function createGetImprovementSuggestionsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_improvement_suggestions', - 'Get AI-powered suggestions to improve the PRD', { - state: z.object({ - filename: z.string(), - }), + description: 'Get AI-powered suggestions to improve the PRD', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, }, async ({ state }) => { const suggestions = await PRDManager.getImprovementSuggestions( @@ -943,13 +1307,15 @@ function createGetImprovementSuggestionsTool(server: McpServer) { } function createGetProjectStatusTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_project_status', - 'Get comprehensive status overview of the PRD project', { - state: z.object({ - filename: z.string(), - }), + description: 'Get comprehensive status overview of the PRD project', + inputSchema: { + state: z.object({ + filename: z.string(), + }), + }, }, async ({ state }) => { const status = await PRDManager.getProjectStatus(state.filename); @@ -970,6 +1336,22 @@ function createGetProjectStatusTool(server: McpServer) { status.blockers.forEach((blocker) => { result += `- ${blocker.title}: ${blocker.notes || 'No details provided'}\n`; }); + result += '\n'; + } + + if (status.highRisks.length > 0) { + result += `**High Risks:**\n`; + status.highRisks.forEach((risk) => { + result += `- ${risk.description} (Owner: ${risk.owner || 'Unassigned'})\n`; + }); + result += '\n'; + } + + if (status.openQuestions.length > 0) { + result += `**Open Questions:**\n`; + status.openQuestions.slice(0, 5).forEach((question) => { + result += `- ${question}\n`; + }); } return { diff --git a/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.service.test.ts b/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.service.test.ts new file mode 100644 index 000000000..b8cf30e2f --- /dev/null +++ b/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.service.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from 'vitest'; + +import { + type KitPrerequisitesDeps, + createKitPrerequisitesService, +} from '../kit-prerequisites.service'; + +function createDeps(overrides: Partial = {}) { + const base: KitPrerequisitesDeps = { + async getVariantFamily() { + return 'supabase'; + }, + async executeCommand(command: string, _args: string[]) { + if (command === 'node') + return { stdout: 'v22.5.0\n', stderr: '', exitCode: 0 }; + if (command === 'pnpm') + return { stdout: '10.19.0\n', stderr: '', exitCode: 0 }; + if (command === 'git') + return { stdout: 'git version 2.44.0\n', stderr: '', exitCode: 0 }; + if (command === 'docker') + return { + stdout: 'Docker version 26.1.0, build abc\n', + stderr: '', + exitCode: 0, + }; + if (command === 'supabase') + return { stdout: '2.75.5\n', stderr: '', exitCode: 0 }; + if (command === 'stripe') throw new Error('not installed'); + + throw new Error(`unexpected command: ${command}`); + }, + }; + + return { + ...base, + ...overrides, + }; +} + +describe('KitPrerequisitesService', () => { + it('returns pass/warn statuses in a healthy supabase setup', async () => { + const service = createKitPrerequisitesService(createDeps()); + const result = await service.check({}); + + expect(result.ready_to_develop).toBe(true); + expect(result.overall).toBe('warn'); + + const node = result.prerequisites.find((item) => item.id === 'node'); + const supabase = result.prerequisites.find( + (item) => item.id === 'supabase', + ); + const stripe = result.prerequisites.find((item) => item.id === 'stripe'); + + expect(node?.status).toBe('pass'); + expect(supabase?.status).toBe('pass'); + expect(stripe?.status).toBe('warn'); + }); + + it('fails when required supabase cli is missing for supabase family', async () => { + const service = createKitPrerequisitesService( + createDeps({ + async executeCommand(command: string, args: string[]) { + if (command === 'supabase') { + throw new Error('missing'); + } + + return createDeps().executeCommand(command, args); + }, + }), + ); + + const result = await service.check({}); + const supabase = result.prerequisites.find( + (item) => item.id === 'supabase', + ); + + expect(supabase?.required).toBe(true); + expect(supabase?.status).toBe('fail'); + expect(result.overall).toBe('fail'); + expect(result.ready_to_develop).toBe(false); + }); + + it('treats supabase cli as optional for orm family', async () => { + const service = createKitPrerequisitesService( + createDeps({ + async getVariantFamily() { + return 'orm'; + }, + async executeCommand(command: string, args: string[]) { + if (command === 'supabase') { + throw new Error('missing'); + } + + return createDeps().executeCommand(command, args); + }, + }), + ); + + const result = await service.check({}); + const supabase = result.prerequisites.find( + (item) => item.id === 'supabase', + ); + + expect(supabase?.required).toBe(false); + expect(supabase?.status).toBe('warn'); + expect(result.overall).toBe('warn'); + expect(result.ready_to_develop).toBe(true); + }); +}); diff --git a/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.tool.test.ts b/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.tool.test.ts new file mode 100644 index 000000000..721a4c6b4 --- /dev/null +++ b/packages/mcp-server/src/tools/prerequisites/__tests__/kit-prerequisites.tool.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; + +import { registerKitPrerequisitesTool } from '../index'; +import { KitPrerequisitesOutputSchema } from '../schema'; + +interface RegisteredTool { + name: string; + handler: (input: unknown) => Promise>; +} + +describe('registerKitPrerequisitesTool', () => { + it('registers kit_prerequisites and returns typed structured output', async () => { + const tools: RegisteredTool[] = []; + + const server = { + registerTool( + name: string, + _config: Record, + handler: (input: unknown) => Promise>, + ) { + tools.push({ name, handler }); + return {}; + }, + }; + + registerKitPrerequisitesTool(server as never); + + expect(tools).toHaveLength(1); + expect(tools[0]?.name).toBe('kit_prerequisites'); + + const result = await tools[0]!.handler({}); + const parsed = KitPrerequisitesOutputSchema.parse(result.structuredContent); + + expect(parsed.prerequisites.length).toBeGreaterThan(0); + expect(typeof parsed.ready_to_develop).toBe('boolean'); + }); +}); diff --git a/packages/mcp-server/src/tools/prerequisites/index.ts b/packages/mcp-server/src/tools/prerequisites/index.ts new file mode 100644 index 000000000..a1874b199 --- /dev/null +++ b/packages/mcp-server/src/tools/prerequisites/index.ts @@ -0,0 +1,191 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { access, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + type KitPrerequisitesDeps, + createKitPrerequisitesService, +} from './kit-prerequisites.service'; +import { + KitPrerequisitesInputSchema, + KitPrerequisitesOutputSchema, +} from './schema'; + +const execFileAsync = promisify(execFile); + +export function registerKitPrerequisitesTool(server: McpServer) { + return server.registerTool( + 'kit_prerequisites', + { + description: 'Check installed tools and versions for this kit variant', + inputSchema: KitPrerequisitesInputSchema, + outputSchema: KitPrerequisitesOutputSchema, + }, + async (input) => { + const parsedInput = KitPrerequisitesInputSchema.parse(input); + + try { + const service = createKitPrerequisitesService( + createKitPrerequisitesDeps(), + ); + + const result = await service.check(parsedInput); + + return { + structuredContent: result, + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_prerequisites failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); +} + +function createKitPrerequisitesDeps(): KitPrerequisitesDeps { + const rootPath = process.cwd(); + + return { + async getVariantFamily() { + const variant = await resolveVariant(rootPath); + return variant.includes('supabase') ? 'supabase' : 'orm'; + }, + async executeCommand(command: string, args: string[]) { + const result = await executeWithFallback(rootPath, command, args); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + }; +} + +async function executeWithFallback( + rootPath: string, + command: string, + args: string[], +) { + try { + return await execFileAsync(command, args, { + cwd: rootPath, + }); + } catch (error) { + // Local CLI tools are often installed in node_modules/.bin in this monorepo. + if (isLocalCliCandidate(command)) { + const localBinCandidates = [ + join(rootPath, 'node_modules', '.bin', command), + join(rootPath, 'apps', 'web', 'node_modules', '.bin', command), + ]; + + for (const localBin of localBinCandidates) { + try { + return await execFileAsync(localBin, args, { + cwd: rootPath, + }); + } catch { + // Try next local binary candidate. + } + } + + try { + return await execFileAsync('pnpm', ['exec', command, ...args], { + cwd: rootPath, + }); + } catch { + return execFileAsync( + 'pnpm', + ['--filter', 'web', 'exec', command, ...args], + { + cwd: rootPath, + }, + ); + } + } + + if (command === 'pnpm') { + return execFileAsync(command, args, { + cwd: rootPath, + }); + } + + throw error; + } +} + +function isLocalCliCandidate(command: string) { + return command === 'supabase' || command === 'stripe'; +} + +async function resolveVariant(rootPath: string) { + const configPath = join(rootPath, '.makerkit', 'config.json'); + + try { + await access(configPath); + const config = JSON.parse(await readFile(configPath, 'utf8')) as Record< + string, + unknown + >; + + const variant = + readString(config, 'variant') ?? + readString(config, 'template') ?? + readString(config, 'kitVariant'); + + if (variant) { + return variant; + } + } catch { + // Fall through to heuristic. + } + + if (await pathExists(join(rootPath, 'apps', 'web', 'supabase'))) { + return 'next-supabase'; + } + + return 'next-drizzle'; +} + +function readString(obj: Record, key: string) { + const value = obj[key]; + return typeof value === 'string' && value.length > 0 ? value : null; +} + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +export { + createKitPrerequisitesService, + type KitPrerequisitesDeps, +} from './kit-prerequisites.service'; +export type { KitPrerequisitesOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/prerequisites/kit-prerequisites.service.ts b/packages/mcp-server/src/tools/prerequisites/kit-prerequisites.service.ts new file mode 100644 index 000000000..0a6bfb36d --- /dev/null +++ b/packages/mcp-server/src/tools/prerequisites/kit-prerequisites.service.ts @@ -0,0 +1,405 @@ +import type { + KitPrerequisiteItem, + KitPrerequisitesInput, + KitPrerequisitesOutput, +} from './schema'; + +type VariantFamily = 'supabase' | 'orm'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface ToolVersion { + installed: boolean; + version: string | null; +} + +export interface KitPrerequisitesDeps { + getVariantFamily(): Promise; + executeCommand(command: string, args: string[]): Promise; +} + +export function createKitPrerequisitesService(deps: KitPrerequisitesDeps) { + return new KitPrerequisitesService(deps); +} + +export class KitPrerequisitesService { + constructor(private readonly deps: KitPrerequisitesDeps) {} + + async check(_input: KitPrerequisitesInput): Promise { + const family = await this.deps.getVariantFamily(); + + const [node, pnpm, git, docker, supabaseCli, stripeCli] = await Promise.all( + [ + this.getNodeVersion(), + this.getPnpmVersion(), + this.getGitVersion(), + this.getDockerVersion(), + this.getSupabaseVersion(), + this.getStripeVersion(), + ], + ); + + const prerequisites: KitPrerequisiteItem[] = []; + + prerequisites.push( + this.createRequiredItem({ + id: 'node', + name: 'Node.js', + minimumVersion: '20.10.0', + installUrl: 'https://nodejs.org', + version: node, + }), + ); + + prerequisites.push( + this.createRequiredItem({ + id: 'pnpm', + name: 'pnpm', + minimumVersion: '10.0.0', + installCommand: 'npm install -g pnpm', + version: pnpm, + }), + ); + + prerequisites.push( + this.createRequiredItem({ + id: 'git', + name: 'Git', + minimumVersion: '2.0.0', + installUrl: 'https://git-scm.com/downloads', + version: git, + }), + ); + + prerequisites.push( + this.createRequiredItem({ + id: 'docker', + name: 'Docker', + minimumVersion: '20.10.0', + installUrl: 'https://docker.com/products/docker-desktop', + requiredFor: + family === 'supabase' ? 'Local Supabase stack' : 'Local PostgreSQL', + version: docker, + }), + ); + + prerequisites.push( + this.createVariantConditionalItem({ + id: 'supabase', + name: 'Supabase CLI', + minimumVersion: '2.0.0', + installCommand: 'npm install -g supabase', + required: family === 'supabase', + requiredFor: 'Supabase variants', + version: supabaseCli, + }), + ); + + prerequisites.push( + this.createVariantConditionalItem({ + id: 'stripe', + name: 'Stripe CLI', + minimumVersion: '1.0.0', + installUrl: 'https://docs.stripe.com/stripe-cli', + required: false, + requiredFor: 'Payment webhook testing', + version: stripeCli, + }), + ); + + const overall = this.computeOverall(prerequisites); + + return { + prerequisites, + overall, + ready_to_develop: overall !== 'fail', + }; + } + + private computeOverall(items: KitPrerequisiteItem[]) { + if (items.some((item) => item.required && item.status === 'fail')) { + return 'fail' as const; + } + + if (items.some((item) => item.status === 'warn')) { + return 'warn' as const; + } + + return 'pass' as const; + } + + private createRequiredItem(params: { + id: string; + name: string; + minimumVersion: string; + version: ToolVersion; + installUrl?: string; + installCommand?: string; + requiredFor?: string; + }): KitPrerequisiteItem { + const status = this.getVersionStatus(params.version, params.minimumVersion); + + return { + id: params.id, + name: params.name, + required: true, + required_for: params.requiredFor, + installed: params.version.installed, + version: params.version.version, + minimum_version: params.minimumVersion, + status, + install_url: params.installUrl, + install_command: params.installCommand, + message: this.getMessage(params.version, params.minimumVersion, true), + remedies: this.getRemedies(params, status, true), + }; + } + + private createVariantConditionalItem(params: { + id: string; + name: string; + minimumVersion: string; + version: ToolVersion; + required: boolean; + requiredFor?: string; + installUrl?: string; + installCommand?: string; + }): KitPrerequisiteItem { + if (!params.required) { + if (!params.version.installed) { + return { + id: params.id, + name: params.name, + required: false, + required_for: params.requiredFor, + installed: false, + version: null, + minimum_version: params.minimumVersion, + status: 'warn', + install_url: params.installUrl, + install_command: params.installCommand, + message: `${params.name} is optional but recommended for ${params.requiredFor ?? 'developer workflows'}.`, + remedies: params.installCommand + ? [params.installCommand] + : params.installUrl + ? [params.installUrl] + : [], + }; + } + + const status = this.getVersionStatus( + params.version, + params.minimumVersion, + ); + + return { + id: params.id, + name: params.name, + required: false, + required_for: params.requiredFor, + installed: true, + version: params.version.version, + minimum_version: params.minimumVersion, + status: status === 'fail' ? 'warn' : status, + install_url: params.installUrl, + install_command: params.installCommand, + message: this.getMessage(params.version, params.minimumVersion, false), + remedies: this.getRemedies( + { + ...params, + }, + status === 'fail' ? 'warn' : status, + false, + ), + }; + } + + const status = this.getVersionStatus(params.version, params.minimumVersion); + + return { + id: params.id, + name: params.name, + required: true, + required_for: params.requiredFor, + installed: params.version.installed, + version: params.version.version, + minimum_version: params.minimumVersion, + status, + install_url: params.installUrl, + install_command: params.installCommand, + message: this.getMessage(params.version, params.minimumVersion, true), + remedies: this.getRemedies(params, status, true), + }; + } + + private getVersionStatus(version: ToolVersion, minimumVersion: string) { + if (!version.installed || !version.version) { + return 'fail' as const; + } + + const cmp = compareVersions(version.version, minimumVersion); + + if (cmp < 0) { + return 'fail' as const; + } + + return 'pass' as const; + } + + private getMessage( + version: ToolVersion, + minimumVersion: string, + required: boolean, + ) { + if (!version.installed) { + return required + ? 'Required tool is not installed.' + : 'Optional tool is not installed.'; + } + + if (!version.version) { + return 'Tool is installed but version could not be detected.'; + } + + const cmp = compareVersions(version.version, minimumVersion); + + if (cmp < 0) { + return `Installed version ${version.version} is below minimum ${minimumVersion}.`; + } + + return `Installed version ${version.version} satisfies minimum ${minimumVersion}.`; + } + + private getRemedies( + params: { + installUrl?: string; + installCommand?: string; + minimumVersion: string; + }, + status: 'pass' | 'warn' | 'fail', + required: boolean, + ) { + if (status === 'pass') { + return []; + } + + const remedies: string[] = []; + + if (params.installCommand) { + remedies.push(params.installCommand); + } + + if (params.installUrl) { + remedies.push(params.installUrl); + } + + remedies.push(`Ensure version is >= ${params.minimumVersion}`); + + if (!required && status === 'warn') { + remedies.push( + 'Optional for development but useful for related workflows', + ); + } + + return remedies; + } + + private async getNodeVersion() { + try { + const result = await this.deps.executeCommand('node', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } + + private async getPnpmVersion() { + try { + const result = await this.deps.executeCommand('pnpm', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } + + private async getGitVersion() { + try { + const result = await this.deps.executeCommand('git', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } + + private async getDockerVersion() { + try { + const result = await this.deps.executeCommand('docker', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } + + private async getSupabaseVersion() { + try { + const result = await this.deps.executeCommand('supabase', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } + + private async getStripeVersion() { + try { + const result = await this.deps.executeCommand('stripe', ['--version']); + return { + installed: true, + version: normalizeVersion(result.stdout), + }; + } catch { + return { installed: false, version: null }; + } + } +} + +function normalizeVersion(input: string) { + const match = input.match(/\d+\.\d+\.\d+/); + + return match ? match[0] : null; +} + +function compareVersions(a: string, b: string) { + const left = a.split('.').map((part) => Number(part)); + const right = b.split('.').map((part) => Number(part)); + + const length = Math.max(left.length, right.length); + + for (let i = 0; i < length; i++) { + const l = left[i] ?? 0; + const r = right[i] ?? 0; + + if (l > r) return 1; + if (l < r) return -1; + } + + return 0; +} diff --git a/packages/mcp-server/src/tools/prerequisites/schema.ts b/packages/mcp-server/src/tools/prerequisites/schema.ts new file mode 100644 index 000000000..ab10c03d7 --- /dev/null +++ b/packages/mcp-server/src/tools/prerequisites/schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod/v3'; + +export const KitPrerequisitesInputSchema = z.object({}); + +export const KitPrerequisiteItemSchema = z.object({ + id: z.string(), + name: z.string(), + required: z.boolean(), + required_for: z.string().optional(), + installed: z.boolean(), + version: z.string().nullable(), + minimum_version: z.string().nullable(), + status: z.enum(['pass', 'warn', 'fail']), + install_url: z.string().optional(), + install_command: z.string().optional(), + message: z.string().optional(), + remedies: z.array(z.string()).default([]), +}); + +export const KitPrerequisitesOutputSchema = z.object({ + prerequisites: z.array(KitPrerequisiteItemSchema), + overall: z.enum(['pass', 'warn', 'fail']), + ready_to_develop: z.boolean(), +}); + +export type KitPrerequisitesInput = z.infer; +export type KitPrerequisiteItem = z.infer; +export type KitPrerequisitesOutput = z.infer< + typeof KitPrerequisitesOutputSchema +>; diff --git a/packages/mcp-server/src/tools/prompts.ts b/packages/mcp-server/src/tools/prompts.ts index 88c9394d6..e8603096c 100644 --- a/packages/mcp-server/src/tools/prompts.ts +++ b/packages/mcp-server/src/tools/prompts.ts @@ -1,5 +1,5 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; +import { z } from 'zod/v3'; interface PromptTemplate { name: string; @@ -280,10 +280,12 @@ export function registerPromptsSystem(server: McpServer) { {} as Record>, ); - server.prompt( + server.registerPrompt( promptTemplate.name, - promptTemplate.description, - argsSchema, + { + description: promptTemplate.description, + argsSchema, + }, async (args: Record) => { const renderedPrompt = PromptsManager.renderPrompt( promptTemplate.name, diff --git a/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.service.test.ts b/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.service.test.ts new file mode 100644 index 000000000..1fe6ff8e8 --- /dev/null +++ b/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.service.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from 'vitest'; + +import { + type RunChecksDeps, + createRunChecksService, +} from '../run-checks.service'; + +function createDeps( + overrides: Partial = {}, + scripts: Record = { + typecheck: 'tsc --noEmit', + 'lint:fix': 'eslint . --fix', + 'format:fix': 'prettier . --write', + test: 'vitest run', + }, +): RunChecksDeps { + let nowValue = 0; + + return { + rootPath: '/repo', + async readRootPackageJson() { + return { scripts }; + }, + async executeCommand() { + nowValue += 100; + return { + stdout: 'ok', + stderr: '', + exitCode: 0, + }; + }, + now() { + return nowValue; + }, + ...overrides, + }; +} + +describe('RunChecksService', () => { + it('runs default scripts and reports pass', async () => { + const service = createRunChecksService(createDeps()); + const result = await service.run({}); + + expect(result.overall).toBe('pass'); + expect(result.scripts_requested).toEqual([ + 'typecheck', + 'lint:fix', + 'format:fix', + ]); + expect(result.summary.passed).toBe(3); + }); + + it('includes tests when includeTests is true', async () => { + const service = createRunChecksService(createDeps()); + const result = await service.run({ state: { includeTests: true } }); + + expect(result.scripts_requested).toContain('test'); + expect(result.summary.total).toBe(4); + }); + + it('marks missing scripts as missing and fails overall', async () => { + const service = createRunChecksService( + createDeps( + {}, + { + typecheck: 'tsc --noEmit', + }, + ), + ); + const result = await service.run({ + state: { scripts: ['typecheck', 'lint:fix'] }, + }); + + expect(result.overall).toBe('fail'); + expect(result.summary.missing).toBe(1); + expect( + result.checks.find((item) => item.script === 'lint:fix')?.status, + ).toBe('missing'); + }); + + it('stops subsequent checks when failFast is enabled', async () => { + let calls = 0; + const service = createRunChecksService( + createDeps({ + async executeCommand(_command, args) { + calls += 1; + if (args[1] === 'typecheck') { + return { stdout: '', stderr: 'boom', exitCode: 1 }; + } + + return { stdout: '', stderr: '', exitCode: 0 }; + }, + }), + ); + + const result = await service.run({ + state: { + scripts: ['typecheck', 'lint:fix', 'format:fix'], + failFast: true, + }, + }); + + expect(calls).toBe(1); + expect(result.checks[0]?.status).toBe('fail'); + expect(result.checks[1]?.status).toBe('skipped'); + expect(result.checks[2]?.status).toBe('skipped'); + }); +}); diff --git a/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.tool.test.ts b/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.tool.test.ts new file mode 100644 index 000000000..8a5c23f3c --- /dev/null +++ b/packages/mcp-server/src/tools/run-checks/__tests__/run-checks.tool.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; + +import { registerRunChecksTool } from '../index'; +import { RunChecksOutputSchema } from '../schema'; + +interface RegisteredTool { + name: string; + handler: (input: unknown) => Promise>; +} + +describe('registerRunChecksTool', () => { + it('registers run_checks and returns typed structured output', async () => { + const tools: RegisteredTool[] = []; + + const server = { + registerTool( + name: string, + _config: Record, + handler: (input: unknown) => Promise>, + ) { + tools.push({ name, handler }); + return {}; + }, + }; + + registerRunChecksTool(server as never); + + expect(tools).toHaveLength(1); + expect(tools[0]?.name).toBe('run_checks'); + + const result = await tools[0]!.handler({}); + const parsed = RunChecksOutputSchema.parse(result.structuredContent); + + expect(parsed.summary.total).toBeGreaterThan(0); + }); +}); diff --git a/packages/mcp-server/src/tools/run-checks/index.ts b/packages/mcp-server/src/tools/run-checks/index.ts new file mode 100644 index 000000000..dd42990ef --- /dev/null +++ b/packages/mcp-server/src/tools/run-checks/index.ts @@ -0,0 +1,115 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + type RunChecksDeps, + createRunChecksService, +} from './run-checks.service'; +import { RunChecksInputSchema, RunChecksOutputSchema } from './schema'; + +const execFileAsync = promisify(execFile); + +export function registerRunChecksTool(server: McpServer) { + const service = createRunChecksService(createRunChecksDeps()); + + return server.registerTool( + 'run_checks', + { + description: + 'Run code quality checks (typecheck, lint, format, and optional tests) with structured output', + inputSchema: RunChecksInputSchema, + outputSchema: RunChecksOutputSchema, + }, + async (input) => { + try { + const parsed = RunChecksInputSchema.parse(input); + const result = await service.run(parsed); + + return { + structuredContent: result, + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `run_checks failed: ${toErrorMessage(error)}`, + }, + ], + }; + } + }, + ); +} + +export function createRunChecksDeps(rootPath = process.cwd()): RunChecksDeps { + return { + rootPath, + async readRootPackageJson() { + const path = join(rootPath, 'package.json'); + const content = await readFile(path, 'utf8'); + return JSON.parse(content) as { scripts?: Record }; + }, + async executeCommand(command, args) { + try { + const result = await execFileAsync(command, args, { + cwd: rootPath, + maxBuffer: 1024 * 1024 * 10, + }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + } catch (error) { + if (isExecError(error)) { + return { + stdout: error.stdout ?? '', + stderr: error.stderr ?? '', + exitCode: error.code, + }; + } + + throw error; + } + }, + now() { + return Date.now(); + }, + }; +} + +interface ExecError extends Error { + code: number; + stdout?: string; + stderr?: string; +} + +function isExecError(error: unknown): error is ExecError { + return error instanceof Error && 'code' in error; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +export { + createRunChecksService, + type RunChecksDeps, +} from './run-checks.service'; +export type { RunChecksOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/run-checks/run-checks.service.ts b/packages/mcp-server/src/tools/run-checks/run-checks.service.ts new file mode 100644 index 000000000..a614094b6 --- /dev/null +++ b/packages/mcp-server/src/tools/run-checks/run-checks.service.ts @@ -0,0 +1,147 @@ +import type { + RunChecksInput, + RunChecksOutput, + RunChecksResult, +} from './schema'; + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +export interface RunChecksDeps { + rootPath: string; + readRootPackageJson(): Promise<{ scripts?: Record }>; + executeCommand(command: string, args: string[]): Promise; + now(): number; +} + +const DEFAULT_SCRIPTS = ['typecheck', 'lint:fix', 'format:fix'] as const; + +export function createRunChecksService(deps: RunChecksDeps) { + return new RunChecksService(deps); +} + +export class RunChecksService { + constructor(private readonly deps: RunChecksDeps) {} + + async run(input: RunChecksInput): Promise { + const options = input.state ?? {}; + const includeTests = options.includeTests ?? false; + const failFast = options.failFast ?? false; + const maxOutputChars = options.maxOutputChars ?? 4000; + + const scriptsRequested = this.resolveScripts(options.scripts, includeTests); + const checks: RunChecksResult[] = []; + + const packageJson = await this.deps.readRootPackageJson(); + const availableScripts = new Set(Object.keys(packageJson.scripts ?? {})); + + let stopRunning = false; + + for (const script of scriptsRequested) { + if (stopRunning) { + checks.push({ + script, + command: `pnpm run ${script}`, + status: 'skipped', + exit_code: null, + duration_ms: 0, + stdout: '', + stderr: '', + message: + 'Skipped because failFast is enabled and a previous check failed.', + }); + continue; + } + + if (!availableScripts.has(script)) { + checks.push({ + script, + command: `pnpm run ${script}`, + status: 'missing', + exit_code: null, + duration_ms: 0, + stdout: '', + stderr: '', + message: `Script "${script}" was not found in root package.json.`, + }); + + if (failFast) { + stopRunning = true; + } + + continue; + } + + const startedAt = this.deps.now(); + const result = await this.deps.executeCommand('pnpm', ['run', script]); + const duration = Math.max(0, this.deps.now() - startedAt); + const status = result.exitCode === 0 ? 'pass' : 'fail'; + + checks.push({ + script, + command: `pnpm run ${script}`, + status, + exit_code: result.exitCode, + duration_ms: duration, + stdout: truncateOutput(result.stdout, maxOutputChars), + stderr: truncateOutput(result.stderr, maxOutputChars), + }); + + if (status === 'fail' && failFast) { + stopRunning = true; + } + } + + const summary = { + total: checks.length, + passed: checks.filter((check) => check.status === 'pass').length, + failed: checks.filter((check) => check.status === 'fail').length, + missing: checks.filter((check) => check.status === 'missing').length, + skipped: checks.filter((check) => check.status === 'skipped').length, + }; + + return { + overall: summary.failed > 0 || summary.missing > 0 ? 'fail' : 'pass', + scripts_requested: scriptsRequested, + checks, + summary, + }; + } + + private resolveScripts(scripts: string[] | undefined, includeTests: boolean) { + const list = scripts && scripts.length > 0 ? scripts : [...DEFAULT_SCRIPTS]; + + if (includeTests) { + list.push('test'); + } + + return dedupe(list); + } +} + +function dedupe(list: string[]) { + const seen = new Set(); + const output: string[] = []; + + for (const item of list) { + if (seen.has(item)) { + continue; + } + + seen.add(item); + output.push(item); + } + + return output; +} + +function truncateOutput(value: string, maxOutputChars: number) { + if (value.length <= maxOutputChars) { + return value; + } + + return `${value.slice(0, maxOutputChars)}\n...[truncated ${value.length - maxOutputChars} chars]`; +} diff --git a/packages/mcp-server/src/tools/run-checks/schema.ts b/packages/mcp-server/src/tools/run-checks/schema.ts new file mode 100644 index 000000000..a5695934e --- /dev/null +++ b/packages/mcp-server/src/tools/run-checks/schema.ts @@ -0,0 +1,40 @@ +import { z } from 'zod/v3'; + +export const RunChecksInputSchema = z.object({ + state: z + .object({ + scripts: z.array(z.string().min(1)).optional(), + includeTests: z.boolean().optional(), + failFast: z.boolean().optional(), + maxOutputChars: z.number().int().min(200).max(20000).optional(), + }) + .optional(), +}); + +export const RunChecksResultSchema = z.object({ + script: z.string(), + command: z.string(), + status: z.enum(['pass', 'fail', 'missing', 'skipped']), + exit_code: z.number().int().nullable(), + duration_ms: z.number().int().min(0), + stdout: z.string(), + stderr: z.string(), + message: z.string().optional(), +}); + +export const RunChecksOutputSchema = z.object({ + overall: z.enum(['pass', 'fail']), + scripts_requested: z.array(z.string()), + checks: z.array(RunChecksResultSchema), + summary: z.object({ + total: z.number().int().min(0), + passed: z.number().int().min(0), + failed: z.number().int().min(0), + missing: z.number().int().min(0), + skipped: z.number().int().min(0), + }), +}); + +export type RunChecksInput = z.infer; +export type RunChecksResult = z.infer; +export type RunChecksOutput = z.infer; diff --git a/packages/mcp-server/src/tools/scripts.ts b/packages/mcp-server/src/tools/scripts.ts index 8fcc1c63f..743db3716 100644 --- a/packages/mcp-server/src/tools/scripts.ts +++ b/packages/mcp-server/src/tools/scripts.ts @@ -1,7 +1,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { z } from 'zod'; +import { z } from 'zod/v3'; interface ScriptInfo { name: string; @@ -241,9 +241,12 @@ export function registerScriptsTools(server: McpServer) { } function createGetScriptsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_scripts', - 'Get all available npm/pnpm scripts with descriptions and usage guidance', + { + description: + 'Get all available npm/pnpm scripts with descriptions and usage guidance', + }, async () => { const scripts = await ScriptsTool.getScripts(); @@ -267,13 +270,15 @@ function createGetScriptsTool(server: McpServer) { } function createGetScriptDetailsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_script_details', - 'Get detailed information about a specific script', { - state: z.object({ - scriptName: z.string(), - }), + description: 'Get detailed information about a specific script', + inputSchema: { + state: z.object({ + scriptName: z.string(), + }), + }, }, async ({ state }) => { const script = await ScriptsTool.getScriptDetails(state.scriptName); @@ -300,9 +305,12 @@ Usage: ${script.usage}${healthcheck}`, } function createGetHealthcheckScriptsTool(server: McpServer) { - return server.tool( + return server.registerTool( 'get_healthcheck_scripts', - 'Get critical scripts that should be run after writing code (typecheck, lint, format, test)', + { + description: + 'Get critical scripts that should be run after writing code (typecheck, lint, format, test)', + }, async () => { const scripts = await ScriptsTool.getScripts(); const healthcheckScripts = scripts.filter((script) => script.healthcheck); diff --git a/packages/mcp-server/src/tools/status/__tests__/kit-status.service.test.ts b/packages/mcp-server/src/tools/status/__tests__/kit-status.service.test.ts new file mode 100644 index 000000000..105a4b6ac --- /dev/null +++ b/packages/mcp-server/src/tools/status/__tests__/kit-status.service.test.ts @@ -0,0 +1,431 @@ +import { describe, expect, it } from 'vitest'; + +import { + type KitStatusDeps, + createKitStatusService, +} from '../kit-status.service'; + +function createDeps(overrides: Partial = {}): KitStatusDeps { + const readJsonMap: Record = { + 'package.json': { + name: 'test-project', + packageManager: 'pnpm@10.0.0', + }, + 'apps/web/package.json': { + dependencies: { + next: '16.1.6', + }, + }, + }; + + return { + rootPath: '/repo', + async readJsonFile(path: string) { + if (!(path in readJsonMap)) { + throw new Error(`missing file: ${path}`); + } + + return readJsonMap[path]; + }, + async pathExists(path: string) { + return path === 'apps/web/supabase'; + }, + async isDirectory(path: string) { + return path === 'node_modules'; + }, + async executeCommand(command: string, args: string[]) { + if (command !== 'git') { + throw new Error('unsupported command'); + } + + if (args[0] === 'rev-parse') { + return { + stdout: 'main\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'status') { + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'symbolic-ref') { + return { + stdout: 'origin/main\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-base') { + return { + stdout: 'abc123\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-tree') { + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + } + + throw new Error('unsupported git args'); + }, + async isPortOpen(port: number) { + return port === 3000 || port === 54321 || port === 54323; + }, + getNodeVersion() { + return 'v22.5.0'; + }, + ...overrides, + }; +} + +describe('KitStatusService', () => { + it('returns a complete status in the happy path', async () => { + const service = createKitStatusService(createDeps()); + + const result = await service.getStatus({}); + + expect(result.project_name).toBe('test-project'); + expect(result.package_manager).toBe('pnpm'); + expect(result.node_version).toBe('22.5.0'); + expect(result.git_branch).toBe('main'); + expect(result.git_clean).toBe(true); + expect(result.deps_installed).toBe(true); + expect(result.variant).toBe('next-supabase'); + expect(result.services.app.running).toBe(true); + expect(result.services.app.port).toBe(3000); + expect(result.services.supabase.running).toBe(true); + expect(result.services.supabase.api_port).toBe(54321); + expect(result.services.supabase.studio_port).toBe(54323); + expect(result.git_modified_files).toHaveLength(0); + expect(result.git_untracked_files).toHaveLength(0); + expect(result.git_merge_check.target_branch).toBe('main'); + expect(result.git_merge_check.has_conflicts).toBe(false); + expect(result.diagnostics).toHaveLength(5); + }); + + it('falls back when git commands fail', async () => { + const service = createKitStatusService( + createDeps({ + async executeCommand() { + throw new Error('git not found'); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.git_branch).toBe('unknown'); + expect(result.git_clean).toBe(false); + expect(result.git_merge_check.detectable).toBe(false); + expect(result.diagnostics.find((item) => item.id === 'git')?.status).toBe( + 'warn', + ); + }); + + it('collects modified files from git status output', async () => { + const service = createKitStatusService( + createDeps({ + async executeCommand(command: string, args: string[]) { + if (command !== 'git') { + throw new Error('unsupported command'); + } + + if (args[0] === 'rev-parse') { + return { + stdout: 'feature/status\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'status') { + return { + stdout: ' M apps/web/page.tsx\n?? new-file.ts\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'symbolic-ref') { + return { + stdout: 'origin/main\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-base') { + return { + stdout: 'abc123\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-tree') { + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + } + + throw new Error('unsupported git args'); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.git_clean).toBe(false); + expect(result.git_modified_files).toEqual(['apps/web/page.tsx']); + expect(result.git_untracked_files).toEqual(['new-file.ts']); + }); + + it('detects merge conflicts against default branch', async () => { + const service = createKitStatusService( + createDeps({ + async executeCommand(command: string, args: string[]) { + if (command !== 'git') { + throw new Error('unsupported command'); + } + + if (args[0] === 'rev-parse') { + return { + stdout: 'feature/conflicts\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'status') { + return { + stdout: '', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'symbolic-ref') { + return { + stdout: 'origin/main\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-base') { + return { + stdout: 'abc123\n', + stderr: '', + exitCode: 0, + }; + } + + if (args[0] === 'merge-tree') { + return { + stdout: + 'CONFLICT (content): Merge conflict in apps/dev-tool/app/page.tsx\n', + stderr: '', + exitCode: 0, + }; + } + + throw new Error('unsupported git args'); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.git_merge_check.target_branch).toBe('main'); + expect(result.git_merge_check.detectable).toBe(true); + expect(result.git_merge_check.has_conflicts).toBe(true); + expect(result.git_merge_check.conflict_files).toEqual([ + 'apps/dev-tool/app/page.tsx', + ]); + expect( + result.diagnostics.find((item) => item.id === 'merge_conflicts')?.status, + ).toBe('warn'); + }); + + it('uses unknown package manager when packageManager is missing', async () => { + const service = createKitStatusService( + createDeps({ + async readJsonFile(path: string) { + if (path === 'package.json') { + return { name: 'test-project' }; + } + + if (path === 'apps/web/package.json') { + return { + dependencies: { + next: '16.1.6', + }, + }; + } + + throw new Error(`missing file: ${path}`); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.package_manager).toBe('unknown'); + }); + + it('provides remedies when services are not running', async () => { + const service = createKitStatusService( + createDeps({ + async isPortOpen() { + return false; + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.services.app.running).toBe(false); + expect(result.services.supabase.running).toBe(false); + + const devServerDiagnostic = result.diagnostics.find( + (item) => item.id === 'dev_server', + ); + const supabaseDiagnostic = result.diagnostics.find( + (item) => item.id === 'supabase', + ); + + expect(devServerDiagnostic?.status).toBe('fail'); + expect(devServerDiagnostic?.remedies).toEqual(['Run pnpm dev']); + expect(supabaseDiagnostic?.status).toBe('fail'); + expect(supabaseDiagnostic?.remedies).toEqual([ + 'Run pnpm supabase:web:start', + ]); + }); + + it('maps variant from .makerkit/config.json when present', async () => { + const service = createKitStatusService( + createDeps({ + async pathExists(path: string) { + return path === '.makerkit/config.json'; + }, + async readJsonFile(path: string) { + if (path === '.makerkit/config.json') { + return { + variant: 'next-prisma', + }; + } + + if (path === 'package.json') { + return { + name: 'test-project', + packageManager: 'pnpm@10.0.0', + }; + } + + throw new Error(`missing file: ${path}`); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.variant).toBe('next-prisma'); + expect(result.variant_family).toBe('orm'); + expect(result.database).toBe('postgresql'); + expect(result.auth).toBe('better-auth'); + }); + + it('reads variant from the template key when present', async () => { + const service = createKitStatusService( + createDeps({ + async pathExists(path: string) { + return path === '.makerkit/config.json'; + }, + async readJsonFile(path: string) { + if (path === '.makerkit/config.json') { + return { + template: 'react-router-supabase', + }; + } + + if (path === 'package.json') { + return { + name: 'test-project', + packageManager: 'pnpm@10.0.0', + }; + } + + throw new Error(`missing file: ${path}`); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.variant).toBe('react-router-supabase'); + expect(result.framework).toBe('react-router'); + }); + + it('reads variant from kitVariant key and preserves unknown names', async () => { + const service = createKitStatusService( + createDeps({ + async pathExists(path: string) { + return path === '.makerkit/config.json'; + }, + async readJsonFile(path: string) { + if (path === '.makerkit/config.json') { + return { + kitVariant: 'custom-enterprise-kit', + }; + } + + if (path === 'package.json') { + return { + name: 'test-project', + packageManager: 'pnpm@10.0.0', + }; + } + + throw new Error(`missing file: ${path}`); + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.variant).toBe('custom-enterprise-kit'); + expect(result.variant_family).toBe('supabase'); + expect(result.framework).toBe('nextjs'); + }); + + it('uses heuristic variant fallback when config is absent', async () => { + const service = createKitStatusService( + createDeps({ + async pathExists(path: string) { + return path === 'apps/web/supabase'; + }, + }), + ); + + const result = await service.getStatus({}); + + expect(result.variant).toBe('next-supabase'); + expect(result.framework).toBe('nextjs'); + expect(result.database).toBe('supabase'); + expect(result.auth).toBe('supabase'); + }); +}); diff --git a/packages/mcp-server/src/tools/status/index.ts b/packages/mcp-server/src/tools/status/index.ts new file mode 100644 index 000000000..1e54d92bc --- /dev/null +++ b/packages/mcp-server/src/tools/status/index.ts @@ -0,0 +1,144 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { execFile } from 'node:child_process'; +import { access, readFile, stat } from 'node:fs/promises'; +import { Socket } from 'node:net'; +import { join } from 'node:path'; +import { promisify } from 'node:util'; + +import { + type KitStatusDeps, + createKitStatusService, +} from './kit-status.service'; +import { KitStatusInputSchema, KitStatusOutputSchema } from './schema'; + +const execFileAsync = promisify(execFile); + +export function registerKitStatusTool(server: McpServer) { + return server.registerTool( + 'kit_status', + { + description: 'Project status with variant context', + inputSchema: KitStatusInputSchema, + outputSchema: KitStatusOutputSchema, + }, + async (input) => { + const parsedInput = KitStatusInputSchema.parse(input); + + try { + const service = createKitStatusService(createKitStatusDeps()); + const status = await service.getStatus(parsedInput); + + return { + structuredContent: status, + content: [ + { + type: 'text', + text: JSON.stringify(status), + }, + ], + }; + } catch (error) { + const message = toErrorMessage(error); + + return { + isError: true, + content: [ + { + type: 'text', + text: `kit_status failed: ${message}`, + }, + ], + }; + } + }, + ); +} + +function createKitStatusDeps(): KitStatusDeps { + const rootPath = process.cwd(); + + return { + rootPath, + async readJsonFile(path: string): Promise { + const filePath = join(rootPath, path); + const content = await readFile(filePath, 'utf8'); + return JSON.parse(content) as unknown; + }, + async pathExists(path: string) { + const fullPath = join(rootPath, path); + + try { + await access(fullPath); + return true; + } catch { + return false; + } + }, + async isDirectory(path: string) { + const fullPath = join(rootPath, path); + + try { + const stats = await stat(fullPath); + return stats.isDirectory(); + } catch { + return false; + } + }, + async executeCommand(command: string, args: string[]) { + const result = await execFileAsync(command, args, { + cwd: rootPath, + }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: 0, + }; + }, + async isPortOpen(port: number) { + return checkPort(port); + }, + getNodeVersion() { + return process.version; + }, + }; +} + +async function checkPort(port: number) { + return new Promise((resolve) => { + const socket = new Socket(); + + socket.setTimeout(200); + + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + + socket.once('timeout', () => { + socket.destroy(); + resolve(false); + }); + + socket.once('error', () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, '127.0.0.1'); + }); +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +export { + createKitStatusService, + type KitStatusDeps, +} from './kit-status.service'; +export type { KitStatusOutput } from './schema'; diff --git a/packages/mcp-server/src/tools/status/kit-status.service.ts b/packages/mcp-server/src/tools/status/kit-status.service.ts new file mode 100644 index 000000000..c8149a0fe --- /dev/null +++ b/packages/mcp-server/src/tools/status/kit-status.service.ts @@ -0,0 +1,549 @@ +import { join } from 'node:path'; + +import type { KitStatusInput, KitStatusOutput } from './schema'; + +interface VariantDescriptor { + variant: string; + variant_family: string; + framework: string; + database: string; + auth: string; +} + +interface CommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface ServicesStatus { + app: { + running: boolean; + port: number | null; + }; + supabase: { + running: boolean; + api_port: number | null; + studio_port: number | null; + }; +} + +interface MergeCheckStatus { + target_branch: string | null; + detectable: boolean; + has_conflicts: boolean | null; + conflict_files: string[]; + message: string; +} + +export interface KitStatusDeps { + rootPath: string; + readJsonFile(path: string): Promise; + pathExists(path: string): Promise; + isDirectory(path: string): Promise; + executeCommand(command: string, args: string[]): Promise; + isPortOpen(port: number): Promise; + getNodeVersion(): string; +} + +export function createKitStatusService(deps: KitStatusDeps) { + return new KitStatusService(deps); +} + +export class KitStatusService { + constructor(private readonly deps: KitStatusDeps) {} + + async getStatus(_input: KitStatusInput): Promise { + const packageJson = await this.readObject('package.json'); + + const projectName = this.readString(packageJson, 'name') ?? 'unknown'; + const packageManager = this.getPackageManager(packageJson); + const depsInstalled = await this.deps.isDirectory('node_modules'); + + const { gitBranch, gitClean, modifiedFiles, untrackedFiles, mergeCheck } = + await this.getGitStatus(); + const variant = await this.resolveVariant(); + const services = await this.getServicesStatus(); + const diagnostics = this.buildDiagnostics({ + depsInstalled, + gitBranch, + gitClean, + mergeCheck, + services, + }); + + return { + ...variant, + project_name: projectName, + node_version: this.deps.getNodeVersion().replace(/^v/, ''), + package_manager: packageManager, + deps_installed: depsInstalled, + git_clean: gitClean, + git_branch: gitBranch, + git_modified_files: modifiedFiles, + git_untracked_files: untrackedFiles, + git_merge_check: mergeCheck, + services, + diagnostics, + }; + } + + private async getServicesStatus(): Promise { + const app = await this.detectAppService(); + const supabase = await this.detectSupabaseService(); + + return { + app, + supabase, + }; + } + + private async detectAppService() { + const commonDevPorts = [3000, 3001, 3002, 3003]; + + for (const port of commonDevPorts) { + if (await this.deps.isPortOpen(port)) { + return { + running: true, + port, + }; + } + } + + return { + running: false, + port: null, + }; + } + + private async detectSupabaseService() { + const apiPort = 54321; + const studioPort = 54323; + + const [apiRunning, studioRunning] = await Promise.all([ + this.deps.isPortOpen(apiPort), + this.deps.isPortOpen(studioPort), + ]); + + return { + running: apiRunning || studioRunning, + api_port: apiRunning ? apiPort : null, + studio_port: studioRunning ? studioPort : null, + }; + } + + private buildDiagnostics(params: { + depsInstalled: boolean; + gitBranch: string; + gitClean: boolean; + mergeCheck: MergeCheckStatus; + services: ServicesStatus; + }) { + const diagnostics: KitStatusOutput['diagnostics'] = []; + + diagnostics.push({ + id: 'dependencies', + status: params.depsInstalled ? 'pass' : 'fail', + message: params.depsInstalled + ? 'Dependencies are installed.' + : 'Dependencies are missing.', + remedies: params.depsInstalled ? [] : ['Run pnpm install'], + }); + + diagnostics.push({ + id: 'dev_server', + status: params.services.app.running ? 'pass' : 'fail', + message: params.services.app.running + ? `Dev server is running on port ${params.services.app.port}.` + : 'Dev server is not running.', + remedies: params.services.app.running ? [] : ['Run pnpm dev'], + }); + + diagnostics.push({ + id: 'supabase', + status: params.services.supabase.running ? 'pass' : 'fail', + message: params.services.supabase.running + ? `Supabase is running${params.services.supabase.api_port ? ` (API ${params.services.supabase.api_port})` : ''}${params.services.supabase.studio_port ? ` (Studio ${params.services.supabase.studio_port})` : ''}.` + : 'Supabase is not running.', + remedies: params.services.supabase.running + ? [] + : ['Run pnpm supabase:web:start'], + }); + + diagnostics.push({ + id: 'git', + status: + params.gitBranch === 'unknown' + ? 'warn' + : params.gitClean + ? 'pass' + : 'warn', + message: + params.gitBranch === 'unknown' + ? 'Git status unavailable.' + : `Current branch ${params.gitBranch} is ${params.gitClean ? 'clean' : 'dirty'}.`, + remedies: + params.gitBranch === 'unknown' || params.gitClean + ? [] + : ['Commit or stash changes when you need a clean workspace'], + }); + + diagnostics.push({ + id: 'merge_conflicts', + status: + params.mergeCheck.has_conflicts === true + ? 'warn' + : params.mergeCheck.detectable + ? 'pass' + : 'warn', + message: params.mergeCheck.message, + remedies: + params.mergeCheck.has_conflicts === true + ? [ + `Rebase or merge ${params.mergeCheck.target_branch} and resolve conflicts`, + ] + : [], + }); + + return diagnostics; + } + + private async getGitStatus() { + try { + const branchResult = await this.deps.executeCommand('git', [ + 'rev-parse', + '--abbrev-ref', + 'HEAD', + ]); + + const statusResult = await this.deps.executeCommand('git', [ + 'status', + '--porcelain', + ]); + + const parsedStatus = this.parseGitStatus(statusResult.stdout); + const mergeCheck = await this.getMergeCheck(); + + return { + gitBranch: branchResult.stdout.trim() || 'unknown', + gitClean: + parsedStatus.modifiedFiles.length === 0 && + parsedStatus.untrackedFiles.length === 0, + modifiedFiles: parsedStatus.modifiedFiles, + untrackedFiles: parsedStatus.untrackedFiles, + mergeCheck, + }; + } catch { + return { + gitBranch: 'unknown', + gitClean: false, + modifiedFiles: [], + untrackedFiles: [], + mergeCheck: { + target_branch: null, + detectable: false, + has_conflicts: null, + conflict_files: [], + message: 'Git metadata unavailable.', + } satisfies MergeCheckStatus, + }; + } + } + + private parseGitStatus(output: string) { + const modifiedFiles: string[] = []; + const untrackedFiles: string[] = []; + + const lines = output.split('\n').filter((line) => line.trim().length > 0); + + for (const line of lines) { + if (line.startsWith('?? ')) { + const path = line.slice(3).trim(); + if (path) { + untrackedFiles.push(path); + } + + continue; + } + + if (line.length >= 4) { + const path = line.slice(3).trim(); + if (path) { + modifiedFiles.push(path); + } + } + } + + return { + modifiedFiles, + untrackedFiles, + }; + } + + private async getMergeCheck(): Promise { + const targetBranch = await this.resolveMergeTargetBranch(); + + if (!targetBranch) { + return { + target_branch: null, + detectable: false, + has_conflicts: null, + conflict_files: [], + message: 'No default target branch found for merge conflict checks.', + }; + } + + try { + const mergeBaseResult = await this.deps.executeCommand('git', [ + 'merge-base', + 'HEAD', + targetBranch, + ]); + + const mergeBase = mergeBaseResult.stdout.trim(); + + if (!mergeBase) { + return { + target_branch: targetBranch, + detectable: false, + has_conflicts: null, + conflict_files: [], + message: 'Unable to compute merge base.', + }; + } + + const mergeTreeResult = await this.deps.executeCommand('git', [ + 'merge-tree', + mergeBase, + 'HEAD', + targetBranch, + ]); + + const rawOutput = `${mergeTreeResult.stdout}\n${mergeTreeResult.stderr}`; + const conflictFiles = this.extractConflictFiles(rawOutput); + const hasConflictMarkers = + /CONFLICT|changed in both|both modified|both added/i.test(rawOutput); + const hasConflicts = conflictFiles.length > 0 || hasConflictMarkers; + + return { + target_branch: targetBranch, + detectable: true, + has_conflicts: hasConflicts, + conflict_files: conflictFiles, + message: hasConflicts + ? `Potential merge conflicts detected against ${targetBranch}.` + : `No merge conflicts detected against ${targetBranch}.`, + }; + } catch { + return { + target_branch: targetBranch, + detectable: false, + has_conflicts: null, + conflict_files: [], + message: 'Merge conflict detection is not available in this git setup.', + }; + } + } + + private extractConflictFiles(rawOutput: string) { + const files = new Set(); + const lines = rawOutput.split('\n'); + + for (const line of lines) { + const conflictMatch = line.match(/CONFLICT .* in (.+)$/); + if (conflictMatch?.[1]) { + files.add(conflictMatch[1].trim()); + } + } + + return Array.from(files).sort((a, b) => a.localeCompare(b)); + } + + private async resolveMergeTargetBranch() { + try { + const originHead = await this.deps.executeCommand('git', [ + 'symbolic-ref', + '--quiet', + '--short', + 'refs/remotes/origin/HEAD', + ]); + + const value = originHead.stdout.trim(); + if (value) { + return value.replace(/^origin\//, ''); + } + } catch { + // Fallback candidates below. + } + + for (const candidate of ['main', 'master']) { + try { + await this.deps.executeCommand('git', [ + 'rev-parse', + '--verify', + candidate, + ]); + return candidate; + } catch { + // Try next. + } + } + + return null; + } + + private async resolveVariant(): Promise { + const explicitVariant = await this.resolveConfiguredVariant(); + + if (explicitVariant) { + return explicitVariant; + } + + const heuristicVariant = await this.resolveHeuristicVariant(); + + if (heuristicVariant) { + return heuristicVariant; + } + + return this.mapVariant('next-supabase'); + } + + private async resolveConfiguredVariant(): Promise { + const configPath = '.makerkit/config.json'; + + if (!(await this.deps.pathExists(configPath))) { + return null; + } + + const config = await this.readObject(configPath); + + const value = + this.readString(config, 'variant') ?? + this.readString(config, 'template') ?? + this.readString(config, 'kitVariant'); + + if (!value) { + return null; + } + + return this.mapVariant(value, { + preserveVariant: true, + }); + } + + private async resolveHeuristicVariant(): Promise { + const hasSupabaseFolder = await this.deps.pathExists('apps/web/supabase'); + + if (!hasSupabaseFolder) { + return null; + } + + const appPackage = await this.readObject( + join('apps', 'web', 'package.json'), + ); + + const hasNextDependency = this.hasDependency(appPackage, 'next'); + + if (hasNextDependency) { + return this.mapVariant('next-supabase'); + } + + return null; + } + + private hasDependency(json: Record, dependency: string) { + const dependencies = this.readObjectValue(json, 'dependencies'); + const devDependencies = this.readObjectValue(json, 'devDependencies'); + + return Boolean( + this.readString(dependencies, dependency) || + this.readString(devDependencies, dependency), + ); + } + + private mapVariant( + variant: string, + options: { + preserveVariant?: boolean; + } = {}, + ): VariantDescriptor { + if (variant === 'next-drizzle') { + return { + variant, + variant_family: 'orm', + framework: 'nextjs', + database: 'postgresql', + auth: 'better-auth', + }; + } + + if (variant === 'next-prisma') { + return { + variant, + variant_family: 'orm', + framework: 'nextjs', + database: 'postgresql', + auth: 'better-auth', + }; + } + + if (variant === 'react-router-supabase') { + return { + variant, + variant_family: 'supabase', + framework: 'react-router', + database: 'supabase', + auth: 'supabase', + }; + } + + return { + variant: options.preserveVariant ? variant : 'next-supabase', + variant_family: 'supabase', + framework: 'nextjs', + database: 'supabase', + auth: 'supabase', + }; + } + + private async readObject(path: string): Promise> { + try { + const value = await this.deps.readJsonFile(path); + + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + + return value as Record; + } catch { + return {}; + } + } + + private readString(obj: Record, key: string) { + const value = obj[key]; + + return typeof value === 'string' && value.length > 0 ? value : null; + } + + private readObjectValue(obj: Record, key: string) { + const value = obj[key]; + + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + + return value as Record; + } + + private getPackageManager(packageJson: Record) { + const packageManager = this.readString(packageJson, 'packageManager'); + + if (!packageManager) { + return 'unknown'; + } + + const [name] = packageManager.split('@'); + return name || 'unknown'; + } +} diff --git a/packages/mcp-server/src/tools/status/schema.ts b/packages/mcp-server/src/tools/status/schema.ts new file mode 100644 index 000000000..0f79bc303 --- /dev/null +++ b/packages/mcp-server/src/tools/status/schema.ts @@ -0,0 +1,48 @@ +import { z } from 'zod/v3'; + +export const KitStatusInputSchema = z.object({}); + +export const KitStatusOutputSchema = z.object({ + variant: z.string(), + variant_family: z.string(), + framework: z.string(), + database: z.string(), + auth: z.string(), + project_name: z.string(), + node_version: z.string(), + package_manager: z.string(), + deps_installed: z.boolean(), + git_clean: z.boolean(), + git_branch: z.string(), + git_modified_files: z.array(z.string()), + git_untracked_files: z.array(z.string()), + git_merge_check: z.object({ + target_branch: z.string().nullable(), + detectable: z.boolean(), + has_conflicts: z.boolean().nullable(), + conflict_files: z.array(z.string()), + message: z.string(), + }), + services: z.object({ + app: z.object({ + running: z.boolean(), + port: z.number().nullable(), + }), + supabase: z.object({ + running: z.boolean(), + api_port: z.number().nullable(), + studio_port: z.number().nullable(), + }), + }), + diagnostics: z.array( + z.object({ + id: z.string(), + status: z.enum(['pass', 'warn', 'fail']), + message: z.string(), + remedies: z.array(z.string()).default([]), + }), + ), +}); + +export type KitStatusInput = z.infer; +export type KitStatusOutput = z.infer; diff --git a/packages/mcp-server/src/tools/translations/__tests__/kit-translations.service.test.ts b/packages/mcp-server/src/tools/translations/__tests__/kit-translations.service.test.ts new file mode 100644 index 000000000..3cb115e81 --- /dev/null +++ b/packages/mcp-server/src/tools/translations/__tests__/kit-translations.service.test.ts @@ -0,0 +1,466 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import { + type KitTranslationsDeps, + createKitTranslationsService, +} from '../kit-translations.service'; + +function createDeps( + files: Record, + directories: string[], +): KitTranslationsDeps & { _files: Record } { + const store = { ...files }; + const dirSet = new Set(directories); + + return { + rootPath: '/repo', + async readFile(filePath: string) { + if (!(filePath in store)) { + const error = new Error( + `ENOENT: no such file: ${filePath}`, + ) as NodeJS.ErrnoException; + error.code = 'ENOENT'; + throw error; + } + + return store[filePath]!; + }, + async writeFile(filePath: string, content: string) { + store[filePath] = content; + }, + async readdir(dirPath: string) { + const entries = new Set(); + + for (const filePath of Object.keys(store)) { + if (path.dirname(filePath) === dirPath) { + entries.add(path.basename(filePath)); + } + } + + for (const dir of dirSet) { + if (path.dirname(dir) === dirPath) { + entries.add(path.basename(dir)); + } + } + + return Array.from(entries.values()); + }, + async stat(targetPath: string) { + if (!dirSet.has(targetPath)) { + const error = new Error( + `ENOENT: no such directory: ${targetPath}`, + ) as NodeJS.ErrnoException; + error.code = 'ENOENT'; + throw error; + } + + return { + isDirectory: () => true, + }; + }, + async fileExists(filePath: string) { + return filePath in store || dirSet.has(filePath); + }, + async mkdir(dirPath: string) { + dirSet.add(dirPath); + }, + async unlink(filePath: string) { + delete store[filePath]; + }, + async rmdir(dirPath: string) { + const prefix = dirPath.endsWith('/') ? dirPath : `${dirPath}/`; + + for (const key of Object.keys(store)) { + if (key.startsWith(prefix)) { + delete store[key]; + } + } + + for (const dir of dirSet) { + if (dir === dirPath || dir.startsWith(prefix)) { + dirSet.delete(dir); + } + } + }, + get _files() { + return store; + }, + }; +} + +describe('KitTranslationsService.list', () => { + it('lists and flattens translations with missing namespace fallback', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({ + header: { title: 'Dashboard' }, + }), + [`${localesRoot}/en/auth.json`]: JSON.stringify({ + login: 'Sign In', + }), + [`${localesRoot}/es/common.json`]: JSON.stringify({ + header: { title: 'Panel' }, + }), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.list(); + + expect(result.base_locale).toBe('en'); + expect(result.locales).toEqual(['en', 'es']); + expect(result.namespaces).toEqual(['auth', 'common']); + expect(result.translations.en.common['header.title']).toBe('Dashboard'); + expect(result.translations.en.auth.login).toBe('Sign In'); + expect(result.translations.es.common['header.title']).toBe('Panel'); + expect(result.translations.es.auth).toEqual({}); + }); +}); + +describe('KitTranslationsService.update', () => { + it('updates nested translation keys', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + await service.update({ + locale: 'en', + namespace: 'common', + key: 'header.title', + value: 'Home', + }); + + const content = deps._files[`${localesRoot}/en/common.json`]!; + expect(JSON.parse(content)).toEqual({ header: { title: 'Home' } }); + }); + + it('rejects paths outside locales root', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect( + service.update({ + locale: '../secrets', + namespace: 'common', + key: 'header.title', + value: 'Oops', + }), + ).rejects.toThrow('locale'); + }); + + it('rejects namespace path segments', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect( + service.update({ + locale: 'en', + namespace: 'nested/common', + key: 'header.title', + value: 'Oops', + }), + ).rejects.toThrow('namespace'); + }); +}); + +describe('KitTranslationsService.stats', () => { + it('computes coverage using base locale keys', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({ + header: { title: 'Dashboard', subtitle: 'Welcome' }, + }), + [`${localesRoot}/es/common.json`]: JSON.stringify({ + header: { title: 'Panel' }, + }), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.stats(); + + expect(result.base_locale).toBe('en'); + expect(result.total_keys).toBe(2); + expect(result.coverage.en.translated).toBe(2); + expect(result.coverage.es.translated).toBe(1); + expect(result.coverage.es.missing).toBe(1); + }); +}); + +describe('KitTranslationsService.addNamespace', () => { + it('creates namespace JSON in all locale directories', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + [`${localesRoot}/es/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.addNamespace({ namespace: 'billing' }); + + expect(result.success).toBe(true); + expect(result.namespace).toBe('billing'); + expect(result.files_created).toHaveLength(2); + expect(deps._files[`${localesRoot}/en/billing.json`]).toBe( + JSON.stringify({}, null, 2), + ); + expect(deps._files[`${localesRoot}/es/billing.json`]).toBe( + JSON.stringify({}, null, 2), + ); + }); + + it('throws if namespace already exists', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect(service.addNamespace({ namespace: 'common' })).rejects.toThrow( + 'already exists', + ); + }); + + it('throws if no locales exist', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + + await expect( + service.addNamespace({ namespace: 'billing' }), + ).rejects.toThrow('No locales exist'); + }); + + it('rejects path traversal in namespace', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect( + service.addNamespace({ namespace: '../secrets' }), + ).rejects.toThrow('namespace'); + + await expect( + service.addNamespace({ namespace: 'foo/bar' }), + ).rejects.toThrow('namespace'); + }); +}); + +describe('KitTranslationsService.addLocale', () => { + it('creates locale directory with namespace files', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({ hello: 'Hello' }), + [`${localesRoot}/en/auth.json`]: JSON.stringify({ login: 'Login' }), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.addLocale({ locale: 'fr' }); + + expect(result.success).toBe(true); + expect(result.locale).toBe('fr'); + expect(result.files_created).toHaveLength(2); + expect(deps._files[`${localesRoot}/fr/auth.json`]).toBe( + JSON.stringify({}, null, 2), + ); + expect(deps._files[`${localesRoot}/fr/common.json`]).toBe( + JSON.stringify({}, null, 2), + ); + }); + + it('throws if locale already exists', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect(service.addLocale({ locale: 'en' })).rejects.toThrow( + 'already exists', + ); + }); + + it('works when no namespaces exist yet', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + const result = await service.addLocale({ locale: 'en' }); + + expect(result.success).toBe(true); + expect(result.files_created).toHaveLength(0); + }); + + it('rejects path traversal in locale', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + + await expect(service.addLocale({ locale: '../hack' })).rejects.toThrow( + 'locale', + ); + + await expect(service.addLocale({ locale: 'foo\\bar' })).rejects.toThrow( + 'locale', + ); + }); +}); + +describe('KitTranslationsService.removeNamespace', () => { + it('deletes namespace files from all locales', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + [`${localesRoot}/en/auth.json`]: JSON.stringify({}), + [`${localesRoot}/es/common.json`]: JSON.stringify({}), + [`${localesRoot}/es/auth.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.removeNamespace({ namespace: 'auth' }); + + expect(result.success).toBe(true); + expect(result.namespace).toBe('auth'); + expect(result.files_removed).toHaveLength(2); + expect(deps._files[`${localesRoot}/en/auth.json`]).toBeUndefined(); + expect(deps._files[`${localesRoot}/es/auth.json`]).toBeUndefined(); + expect(deps._files[`${localesRoot}/en/common.json`]).toBeDefined(); + }); + + it('throws if namespace does not exist', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`], + ); + + const service = createKitTranslationsService(deps); + + await expect( + service.removeNamespace({ namespace: 'nonexistent' }), + ).rejects.toThrow('does not exist'); + }); + + it('rejects path traversal', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + + await expect( + service.removeNamespace({ namespace: '../etc' }), + ).rejects.toThrow('namespace'); + }); +}); + +describe('KitTranslationsService.removeLocale', () => { + it('deletes entire locale directory', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + [`${localesRoot}/es/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + const result = await service.removeLocale({ locale: 'es' }); + + expect(result.success).toBe(true); + expect(result.locale).toBe('es'); + expect(result.path_removed).toBe(`${localesRoot}/es`); + expect(deps._files[`${localesRoot}/es/common.json`]).toBeUndefined(); + expect(deps._files[`${localesRoot}/en/common.json`]).toBeDefined(); + }); + + it('throws if locale does not exist', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + + await expect(service.removeLocale({ locale: 'fr' })).rejects.toThrow( + 'does not exist', + ); + }); + + it('throws when trying to delete base locale', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps( + { + [`${localesRoot}/en/common.json`]: JSON.stringify({}), + [`${localesRoot}/es/common.json`]: JSON.stringify({}), + }, + [localesRoot, `${localesRoot}/en`, `${localesRoot}/es`], + ); + + const service = createKitTranslationsService(deps); + + await expect(service.removeLocale({ locale: 'en' })).rejects.toThrow( + 'Cannot remove base locale', + ); + }); + + it('rejects path traversal', async () => { + const localesRoot = '/repo/apps/web/public/locales'; + const deps = createDeps({}, [localesRoot]); + + const service = createKitTranslationsService(deps); + + await expect(service.removeLocale({ locale: '../hack' })).rejects.toThrow( + 'locale', + ); + }); +}); diff --git a/packages/mcp-server/src/tools/translations/index.ts b/packages/mcp-server/src/tools/translations/index.ts new file mode 100644 index 000000000..5fe0e598c --- /dev/null +++ b/packages/mcp-server/src/tools/translations/index.ts @@ -0,0 +1,219 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; + +import { + type KitTranslationsDeps, + createKitTranslationsDeps, + createKitTranslationsService, +} from './kit-translations.service'; +import { + KitTranslationsAddLocaleInputSchema, + KitTranslationsAddLocaleOutputSchema, + KitTranslationsAddNamespaceInputSchema, + KitTranslationsAddNamespaceOutputSchema, + KitTranslationsListInputSchema, + KitTranslationsListOutputSchema, + KitTranslationsRemoveLocaleInputSchema, + KitTranslationsRemoveLocaleOutputSchema, + KitTranslationsRemoveNamespaceInputSchema, + KitTranslationsRemoveNamespaceOutputSchema, + KitTranslationsStatsInputSchema, + KitTranslationsStatsOutputSchema, + KitTranslationsUpdateInputSchema, + KitTranslationsUpdateOutputSchema, +} from './schema'; + +type TextContent = { + type: 'text'; + text: string; +}; + +export function registerKitTranslationsTools(server: McpServer) { + const service = createKitTranslationsService(createKitTranslationsDeps()); + + server.registerTool( + 'kit_translations_list', + { + description: 'List translations across locales and namespaces', + inputSchema: KitTranslationsListInputSchema, + outputSchema: KitTranslationsListOutputSchema, + }, + async () => { + try { + const result = await service.list(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_list', error); + } + }, + ); + + server.registerTool( + 'kit_translations_update', + { + description: 'Update a translation value in a locale namespace', + inputSchema: KitTranslationsUpdateInputSchema, + outputSchema: KitTranslationsUpdateOutputSchema, + }, + async (input) => { + try { + const parsed = KitTranslationsUpdateInputSchema.parse(input); + const result = await service.update(parsed); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_update', error); + } + }, + ); + + server.registerTool( + 'kit_translations_stats', + { + description: 'Get translation coverage statistics', + inputSchema: KitTranslationsStatsInputSchema, + outputSchema: KitTranslationsStatsOutputSchema, + }, + async () => { + try { + const result = await service.stats(); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_stats', error); + } + }, + ); + + server.registerTool( + 'kit_translations_add_namespace', + { + description: 'Create a new translation namespace across all locales', + inputSchema: KitTranslationsAddNamespaceInputSchema, + outputSchema: KitTranslationsAddNamespaceOutputSchema, + }, + async (input) => { + try { + const { namespace } = + KitTranslationsAddNamespaceInputSchema.parse(input); + const result = await service.addNamespace({ namespace }); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_add_namespace', error); + } + }, + ); + + server.registerTool( + 'kit_translations_add_locale', + { + description: 'Add a new locale with empty namespace files', + inputSchema: KitTranslationsAddLocaleInputSchema, + outputSchema: KitTranslationsAddLocaleOutputSchema, + }, + async (input) => { + try { + const { locale } = KitTranslationsAddLocaleInputSchema.parse(input); + const result = await service.addLocale({ locale }); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_add_locale', error); + } + }, + ); + + server.registerTool( + 'kit_translations_remove_namespace', + { + description: 'Remove a translation namespace from all locales', + inputSchema: KitTranslationsRemoveNamespaceInputSchema, + outputSchema: KitTranslationsRemoveNamespaceOutputSchema, + }, + async (input) => { + try { + const { namespace } = + KitTranslationsRemoveNamespaceInputSchema.parse(input); + const result = await service.removeNamespace({ namespace }); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_remove_namespace', error); + } + }, + ); + + server.registerTool( + 'kit_translations_remove_locale', + { + description: 'Remove a locale and all its translation files', + inputSchema: KitTranslationsRemoveLocaleInputSchema, + outputSchema: KitTranslationsRemoveLocaleOutputSchema, + }, + async (input) => { + try { + const { locale } = KitTranslationsRemoveLocaleInputSchema.parse(input); + const result = await service.removeLocale({ locale }); + + return { + structuredContent: result, + content: buildTextContent(JSON.stringify(result)), + }; + } catch (error) { + return buildErrorResponse('kit_translations_remove_locale', error); + } + }, + ); +} + +function buildErrorResponse(tool: string, error: unknown) { + const message = `${tool} failed: ${toErrorMessage(error)}`; + + return { + isError: true, + content: buildTextContent(message), + }; +} + +function toErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return 'Unknown error'; +} + +function buildTextContent(text: string): TextContent[] { + return [{ type: 'text', text }]; +} + +export { createKitTranslationsService, createKitTranslationsDeps }; +export type { KitTranslationsDeps }; +export type { + KitTranslationsAddLocaleOutput, + KitTranslationsAddNamespaceOutput, + KitTranslationsListOutput, + KitTranslationsRemoveLocaleOutput, + KitTranslationsRemoveNamespaceOutput, + KitTranslationsStatsOutput, + KitTranslationsUpdateOutput, +} from './schema'; diff --git a/packages/mcp-server/src/tools/translations/kit-translations.service.ts b/packages/mcp-server/src/tools/translations/kit-translations.service.ts new file mode 100644 index 000000000..960e84d43 --- /dev/null +++ b/packages/mcp-server/src/tools/translations/kit-translations.service.ts @@ -0,0 +1,535 @@ +import path from 'node:path'; + +import type { + KitTranslationsAddLocaleInput, + KitTranslationsAddLocaleSuccess, + KitTranslationsAddNamespaceInput, + KitTranslationsAddNamespaceSuccess, + KitTranslationsListSuccess, + KitTranslationsRemoveLocaleInput, + KitTranslationsRemoveLocaleSuccess, + KitTranslationsRemoveNamespaceInput, + KitTranslationsRemoveNamespaceSuccess, + KitTranslationsStatsSuccess, + KitTranslationsUpdateInput, + KitTranslationsUpdateSuccess, +} from './schema'; + +export interface KitTranslationsDeps { + rootPath: string; + readFile(filePath: string): Promise; + writeFile(filePath: string, content: string): Promise; + readdir(dirPath: string): Promise; + stat(path: string): Promise<{ isDirectory(): boolean }>; + fileExists(filePath: string): Promise; + mkdir(dirPath: string): Promise; + unlink(filePath: string): Promise; + rmdir(dirPath: string): Promise; +} + +export function createKitTranslationsService(deps: KitTranslationsDeps) { + return new KitTranslationsService(deps); +} + +export class KitTranslationsService { + constructor(private readonly deps: KitTranslationsDeps) {} + + async list(): Promise> { + const localesRoot = this.getLocalesRoot(); + const locales = await this.getLocaleDirectories(localesRoot); + const translations: Record< + string, + Record> + > = {}; + const namespaces = new Set(); + + for (const locale of locales) { + const localeDir = this.resolveLocaleDir(localesRoot, locale); + const files = await this.deps.readdir(localeDir); + const jsonFiles = files.filter((file) => file.endsWith('.json')); + + translations[locale] = {}; + + for (const file of jsonFiles) { + const namespace = file.replace(/\.json$/, ''); + const filePath = path.join(localeDir, file); + + namespaces.add(namespace); + + translations[locale][namespace] = + await this.readFlatTranslations(filePath); + } + } + + const namespaceList = Array.from(namespaces).sort(); + + for (const locale of locales) { + for (const namespace of namespaceList) { + if (!translations[locale]?.[namespace]) { + translations[locale]![namespace] = {}; + } + } + } + + return { + base_locale: locales[0] ?? '', + locales, + namespaces: namespaceList, + translations, + }; + } + + async update( + input: KitTranslationsUpdateInput, + ): Promise> { + const localesRoot = this.getLocalesRoot(); + assertSinglePathSegment('locale', input.locale); + assertSinglePathSegment('namespace', input.namespace); + const localeDir = this.resolveLocaleDir(localesRoot, input.locale); + const namespacePath = this.resolveNamespaceFile(localeDir, input.namespace); + + const localeExists = await this.isDirectory(localeDir); + + if (!localeExists) { + throw new Error(`Locale "${input.locale}" does not exist`); + } + + if (!(await this.deps.fileExists(namespacePath))) { + throw new Error( + `Namespace "${input.namespace}" does not exist for locale "${input.locale}"`, + ); + } + + const content = await this.deps.readFile(namespacePath); + const parsed = this.parseJson(content, namespacePath); + const keys = input.key.split('.').filter(Boolean); + + if (keys.length === 0) { + throw new Error('Translation key must not be empty'); + } + + setNestedValue(parsed, keys, input.value); + + await this.deps.writeFile(namespacePath, JSON.stringify(parsed, null, 2)); + + return { + success: true, + file: namespacePath, + }; + } + + async stats(): Promise> { + const { base_locale, locales, namespaces, translations } = + await this.list(); + const baseTranslations = translations[base_locale] ?? {}; + const baseKeys = new Set(); + + for (const namespace of namespaces) { + const entries = Object.keys(baseTranslations[namespace] ?? {}); + + for (const key of entries) { + baseKeys.add(`${namespace}:${key}`); + } + } + + const totalKeys = baseKeys.size; + const coverage: Record< + string, + { total: number; translated: number; missing: number; percentage: number } + > = {}; + + for (const locale of locales) { + let translated = 0; + + for (const compositeKey of baseKeys) { + const [namespace, key] = compositeKey.split(':'); + const value = translations[locale]?.[namespace]?.[key]; + + if (typeof value === 'string' && value.length > 0) { + translated += 1; + } + } + + const missing = totalKeys - translated; + const percentage = + totalKeys === 0 + ? 100 + : Number(((translated / totalKeys) * 100).toFixed(1)); + + coverage[locale] = { + total: totalKeys, + translated, + missing, + percentage, + }; + } + + return { + base_locale, + locale_count: locales.length, + namespace_count: namespaces.length, + total_keys: totalKeys, + coverage, + }; + } + + async addNamespace( + input: KitTranslationsAddNamespaceInput, + ): Promise> { + const localesRoot = this.getLocalesRoot(); + assertSinglePathSegment('namespace', input.namespace); + + const locales = await this.getLocaleDirectories(localesRoot); + + if (locales.length === 0) { + throw new Error('No locales exist yet'); + } + + const filesCreated: string[] = []; + + for (const locale of locales) { + const localeDir = this.resolveLocaleDir(localesRoot, locale); + const namespacePath = this.resolveNamespaceFile( + localeDir, + input.namespace, + ); + + if (await this.deps.fileExists(namespacePath)) { + throw new Error(`Namespace "${input.namespace}" already exists`); + } + } + + try { + for (const locale of locales) { + const localeDir = this.resolveLocaleDir(localesRoot, locale); + const namespacePath = this.resolveNamespaceFile( + localeDir, + input.namespace, + ); + + await this.deps.writeFile(namespacePath, JSON.stringify({}, null, 2)); + filesCreated.push(namespacePath); + } + } catch (error) { + for (const createdFile of filesCreated) { + try { + await this.deps.unlink(createdFile); + } catch { + // best-effort cleanup + } + } + + throw error; + } + + return { + success: true, + namespace: input.namespace, + files_created: filesCreated, + }; + } + + async addLocale( + input: KitTranslationsAddLocaleInput, + ): Promise> { + const localesRoot = this.getLocalesRoot(); + assertSinglePathSegment('locale', input.locale); + + const localeDir = this.resolveLocaleDir(localesRoot, input.locale); + + if (await this.isDirectory(localeDir)) { + throw new Error(`Locale "${input.locale}" already exists`); + } + + const existingLocales = await this.getLocaleDirectories(localesRoot); + const namespaces = new Set(); + + for (const locale of existingLocales) { + const dir = this.resolveLocaleDir(localesRoot, locale); + const files = await this.deps.readdir(dir); + + for (const file of files) { + if (file.endsWith('.json')) { + namespaces.add(file.replace(/\.json$/, '')); + } + } + } + + await this.deps.mkdir(localeDir); + + const filesCreated: string[] = []; + + try { + for (const namespace of Array.from(namespaces).sort()) { + const namespacePath = this.resolveNamespaceFile(localeDir, namespace); + await this.deps.writeFile(namespacePath, JSON.stringify({}, null, 2)); + filesCreated.push(namespacePath); + } + } catch (error) { + for (const createdFile of filesCreated) { + try { + await this.deps.unlink(createdFile); + } catch { + // best-effort cleanup + } + } + + try { + await this.deps.rmdir(localeDir); + } catch { + // best-effort cleanup + } + + throw error; + } + + return { + success: true, + locale: input.locale, + files_created: filesCreated, + }; + } + + async removeNamespace( + input: KitTranslationsRemoveNamespaceInput, + ): Promise> { + const localesRoot = this.getLocalesRoot(); + assertSinglePathSegment('namespace', input.namespace); + + const locales = await this.getLocaleDirectories(localesRoot); + const filesRemoved: string[] = []; + + for (const locale of locales) { + const localeDir = this.resolveLocaleDir(localesRoot, locale); + const namespacePath = this.resolveNamespaceFile( + localeDir, + input.namespace, + ); + + if (await this.deps.fileExists(namespacePath)) { + await this.deps.unlink(namespacePath); + filesRemoved.push(namespacePath); + } + } + + if (filesRemoved.length === 0) { + throw new Error(`Namespace "${input.namespace}" does not exist`); + } + + return { + success: true, + namespace: input.namespace, + files_removed: filesRemoved, + }; + } + + async removeLocale( + input: KitTranslationsRemoveLocaleInput, + ): Promise> { + const localesRoot = this.getLocalesRoot(); + assertSinglePathSegment('locale', input.locale); + + const localeDir = this.resolveLocaleDir(localesRoot, input.locale); + + if (!(await this.isDirectory(localeDir))) { + throw new Error(`Locale "${input.locale}" does not exist`); + } + + const locales = await this.getLocaleDirectories(localesRoot); + const baseLocale = locales[0]; + + if (input.locale === baseLocale) { + throw new Error(`Cannot remove base locale "${input.locale}"`); + } + + await this.deps.rmdir(localeDir); + + return { + success: true, + locale: input.locale, + path_removed: localeDir, + }; + } + + private async getLocaleDirectories(localesRoot: string) { + if (!(await this.deps.fileExists(localesRoot))) { + return []; + } + + const entries = await this.deps.readdir(localesRoot); + const locales: string[] = []; + + for (const entry of entries) { + const fullPath = path.join(localesRoot, entry); + + if (await this.isDirectory(fullPath)) { + locales.push(entry); + } + } + + return locales.sort(); + } + + private async isDirectory(targetPath: string) { + try { + const stats = await this.deps.stat(targetPath); + return stats.isDirectory(); + } catch { + return false; + } + } + + private async readFlatTranslations(filePath: string) { + try { + const content = await this.deps.readFile(filePath); + const parsed = this.parseJson(content, filePath); + return flattenTranslations(parsed); + } catch { + return {}; + } + } + + private parseJson(content: string, filePath: string) { + try { + return JSON.parse(content) as Record; + } catch { + throw new Error(`Invalid JSON in ${filePath}`); + } + } + + private resolveLocaleDir(localesRoot: string, locale: string) { + const resolved = path.resolve(localesRoot, locale); + return ensureInsideRoot(resolved, localesRoot, locale); + } + + private resolveNamespaceFile(localeDir: string, namespace: string) { + const resolved = path.resolve(localeDir, `${namespace}.json`); + return ensureInsideRoot(resolved, localeDir, namespace); + } + + private getLocalesRoot() { + return path.resolve(this.deps.rootPath, 'apps', 'web', 'public', 'locales'); + } +} + +function ensureInsideRoot(resolved: string, root: string, input: string) { + const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`; + + if (!resolved.startsWith(rootWithSep) && resolved !== root) { + throw new Error( + `Invalid path: "${input}" resolves outside the locales root`, + ); + } + + return resolved; +} + +function flattenTranslations( + obj: Record, + prefix = '', + result: Record = {}, +) { + for (const [key, value] of Object.entries(obj)) { + const newKey = prefix ? `${prefix}.${key}` : key; + + if (typeof value === 'string') { + result[newKey] = value; + continue; + } + + if (value && typeof value === 'object') { + flattenTranslations(value as Record, newKey, result); + continue; + } + + if (value !== undefined) { + result[newKey] = String(value); + } + } + + return result; +} + +function setNestedValue( + target: Record, + keys: string[], + value: string, +) { + let current = target; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]!; + const next = current[key]; + + if (!next || typeof next !== 'object') { + current[key] = {}; + } + + current = current[key] as Record; + } + + current[keys[keys.length - 1]!] = value; +} + +function assertSinglePathSegment(name: string, value: string) { + if (value === '.' || value === '..') { + throw new Error(`${name} must be a valid path segment`); + } + + if (value.includes('..')) { + throw new Error(`${name} must not contain ".."`); + } + + if (value.includes('/') || value.includes('\\')) { + throw new Error(`${name} must not include path separators`); + } + + if (value.includes('\0')) { + throw new Error(`${name} must not contain null bytes`); + } +} + +export function createKitTranslationsDeps( + rootPath = process.cwd(), +): KitTranslationsDeps { + return { + rootPath, + async readFile(filePath: string) { + const fs = await import('node:fs/promises'); + return fs.readFile(filePath, 'utf8'); + }, + async writeFile(filePath: string, content: string) { + const fs = await import('node:fs/promises'); + await fs.writeFile(filePath, content, 'utf8'); + }, + async readdir(dirPath: string) { + const fs = await import('node:fs/promises'); + return fs.readdir(dirPath); + }, + async stat(pathname: string) { + const fs = await import('node:fs/promises'); + return fs.stat(pathname); + }, + async fileExists(filePath: string) { + const fs = await import('node:fs/promises'); + try { + await fs.access(filePath); + return true; + } catch { + return false; + } + }, + async mkdir(dirPath: string) { + const fs = await import('node:fs/promises'); + await fs.mkdir(dirPath, { recursive: true }); + }, + async unlink(filePath: string) { + const fs = await import('node:fs/promises'); + await fs.unlink(filePath); + }, + async rmdir(dirPath: string) { + const fs = await import('node:fs/promises'); + await fs.rm(dirPath, { recursive: true, force: true }); + }, + }; +} diff --git a/packages/mcp-server/src/tools/translations/schema.ts b/packages/mcp-server/src/tools/translations/schema.ts new file mode 100644 index 000000000..f13eb0d62 --- /dev/null +++ b/packages/mcp-server/src/tools/translations/schema.ts @@ -0,0 +1,183 @@ +import { z } from 'zod/v3'; + +export const KitTranslationsListInputSchema = z.object({}); + +const FlatTranslationsSchema = z.record(z.string(), z.string()); +const NamespaceTranslationsSchema = z.record( + z.string(), + FlatTranslationsSchema, +); + +const KitTranslationsListSuccessOutputSchema = z.object({ + base_locale: z.string(), + locales: z.array(z.string()), + namespaces: z.array(z.string()), + translations: z.record(z.string(), NamespaceTranslationsSchema), +}); + +export const KitTranslationsListOutputSchema = + KitTranslationsListSuccessOutputSchema; + +export const KitTranslationsUpdateInputSchema = z.object({ + locale: z.string().min(1), + namespace: z.string().min(1), + key: z.string().min(1), + value: z.string(), +}); + +const KitTranslationsUpdateSuccessOutputSchema = z.object({ + success: z.boolean(), + file: z.string(), +}); + +export const KitTranslationsUpdateOutputSchema = + KitTranslationsUpdateSuccessOutputSchema; + +export const KitTranslationsStatsInputSchema = z.object({}); + +const KitTranslationsStatsSuccessOutputSchema = z.object({ + base_locale: z.string(), + locale_count: z.number(), + namespace_count: z.number(), + total_keys: z.number(), + coverage: z.record( + z.string(), + z.object({ + total: z.number(), + translated: z.number(), + missing: z.number(), + percentage: z.number(), + }), + ), +}); + +export const KitTranslationsStatsOutputSchema = + KitTranslationsStatsSuccessOutputSchema; + +export type KitTranslationsListInput = z.infer< + typeof KitTranslationsListInputSchema +>; +export type KitTranslationsListSuccess = z.infer< + typeof KitTranslationsListSuccessOutputSchema +>; +export type KitTranslationsListOutput = z.infer< + typeof KitTranslationsListOutputSchema +>; +export type KitTranslationsUpdateInput = z.infer< + typeof KitTranslationsUpdateInputSchema +>; +export type KitTranslationsUpdateSuccess = z.infer< + typeof KitTranslationsUpdateSuccessOutputSchema +>; +export type KitTranslationsUpdateOutput = z.infer< + typeof KitTranslationsUpdateOutputSchema +>; +export type KitTranslationsStatsInput = z.infer< + typeof KitTranslationsStatsInputSchema +>; +export type KitTranslationsStatsSuccess = z.infer< + typeof KitTranslationsStatsSuccessOutputSchema +>; +export type KitTranslationsStatsOutput = z.infer< + typeof KitTranslationsStatsOutputSchema +>; + +// --- Add Namespace --- + +export const KitTranslationsAddNamespaceInputSchema = z.object({ + namespace: z.string().min(1), +}); + +const KitTranslationsAddNamespaceSuccessOutputSchema = z.object({ + success: z.boolean(), + namespace: z.string(), + files_created: z.array(z.string()), +}); + +export const KitTranslationsAddNamespaceOutputSchema = + KitTranslationsAddNamespaceSuccessOutputSchema; + +export type KitTranslationsAddNamespaceInput = z.infer< + typeof KitTranslationsAddNamespaceInputSchema +>; +export type KitTranslationsAddNamespaceSuccess = z.infer< + typeof KitTranslationsAddNamespaceSuccessOutputSchema +>; +export type KitTranslationsAddNamespaceOutput = z.infer< + typeof KitTranslationsAddNamespaceOutputSchema +>; + +// --- Add Locale --- + +export const KitTranslationsAddLocaleInputSchema = z.object({ + locale: z.string().min(1), +}); + +const KitTranslationsAddLocaleSuccessOutputSchema = z.object({ + success: z.boolean(), + locale: z.string(), + files_created: z.array(z.string()), +}); + +export const KitTranslationsAddLocaleOutputSchema = + KitTranslationsAddLocaleSuccessOutputSchema; + +export type KitTranslationsAddLocaleInput = z.infer< + typeof KitTranslationsAddLocaleInputSchema +>; +export type KitTranslationsAddLocaleSuccess = z.infer< + typeof KitTranslationsAddLocaleSuccessOutputSchema +>; +export type KitTranslationsAddLocaleOutput = z.infer< + typeof KitTranslationsAddLocaleOutputSchema +>; + +// --- Remove Namespace --- + +export const KitTranslationsRemoveNamespaceInputSchema = z.object({ + namespace: z.string().min(1), +}); + +const KitTranslationsRemoveNamespaceSuccessOutputSchema = z.object({ + success: z.boolean(), + namespace: z.string(), + files_removed: z.array(z.string()), +}); + +export const KitTranslationsRemoveNamespaceOutputSchema = + KitTranslationsRemoveNamespaceSuccessOutputSchema; + +export type KitTranslationsRemoveNamespaceInput = z.infer< + typeof KitTranslationsRemoveNamespaceInputSchema +>; +export type KitTranslationsRemoveNamespaceSuccess = z.infer< + typeof KitTranslationsRemoveNamespaceSuccessOutputSchema +>; +export type KitTranslationsRemoveNamespaceOutput = z.infer< + typeof KitTranslationsRemoveNamespaceOutputSchema +>; + +// --- Remove Locale --- + +export const KitTranslationsRemoveLocaleInputSchema = z.object({ + locale: z.string().min(1), +}); + +const KitTranslationsRemoveLocaleSuccessOutputSchema = z.object({ + success: z.boolean(), + locale: z.string(), + path_removed: z.string(), +}); + +export const KitTranslationsRemoveLocaleOutputSchema = + KitTranslationsRemoveLocaleSuccessOutputSchema; + +export type KitTranslationsRemoveLocaleInput = z.infer< + typeof KitTranslationsRemoveLocaleInputSchema +>; +export type KitTranslationsRemoveLocaleSuccess = z.infer< + typeof KitTranslationsRemoveLocaleSuccessOutputSchema +>; +export type KitTranslationsRemoveLocaleOutput = z.infer< + typeof KitTranslationsRemoveLocaleOutputSchema +>; diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json index afbdd3524..9b23e7c39 100644 --- a/packages/mcp-server/tsconfig.json +++ b/packages/mcp-server/tsconfig.json @@ -1,14 +1,15 @@ { - "extends": "@kit/tsconfig/base.json", "compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", "outDir": "./build", "noEmit": false, "strict": false, - "target": "ES2022", + "target": "ES2024", + "allowSyntheticDefaultImports": true, + "jsx": "react-jsx", "module": "nodenext", "moduleResolution": "nodenext" }, "include": ["src"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "src/**/__tests__"] } diff --git a/packages/mcp-server/tsup.config.ts b/packages/mcp-server/tsup.config.ts new file mode 100644 index 000000000..91bedfc92 --- /dev/null +++ b/packages/mcp-server/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig((options) => ({ + entry: ['src/index.ts'], + outDir: 'build', + target: 'es2024', + dts: false, + clean: true, + format: ['cjs'], + ...options, +})); diff --git a/packages/mcp-server/vitest.config.ts b/packages/mcp-server/vitest.config.ts new file mode 100644 index 000000000..bfaebe3ce --- /dev/null +++ b/packages/mcp-server/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + globals: true, + }, +}); diff --git a/packages/monitoring/api/src/get-monitoring-provider.ts b/packages/monitoring/api/src/get-monitoring-provider.ts index 5b3cc4a24..ed6d202d6 100644 --- a/packages/monitoring/api/src/get-monitoring-provider.ts +++ b/packages/monitoring/api/src/get-monitoring-provider.ts @@ -7,12 +7,26 @@ const MONITORING_PROVIDERS = [ ] as const; export const MONITORING_PROVIDER = z - .enum(MONITORING_PROVIDERS) + .enum(MONITORING_PROVIDERS, { + errorMap: () => ({ message: 'Invalid monitoring provider' }), + }) .optional() .transform((value) => value || undefined); export type MonitoringProvider = z.infer; export function getMonitoringProvider() { - return MONITORING_PROVIDER.parse(process.env.NEXT_PUBLIC_MONITORING_PROVIDER); + const provider = MONITORING_PROVIDER.safeParse( + process.env.NEXT_PUBLIC_MONITORING_PROVIDER, + ); + + if (!provider.success) { + console.error( + `Error: Invalid monitoring provider\n\n${provider.error.message}.\n\nWill fallback to console service.\nPlease review the variable NEXT_PUBLIC_MONITORING_PROVIDER`, + ); + + return; + } + + return provider.data; } diff --git a/packages/shared/package.json b/packages/shared/package.json index b522d29a3..5c941fd38 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -20,10 +20,11 @@ "@kit/eslint-config": "workspace:*", "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", + "@types/node": "catalog:", "@types/react": "catalog:" }, "dependencies": { - "pino": "^10.3.0" + "pino": "catalog:" }, "typesVersions": { "*": { diff --git a/packages/ui/package.json b/packages/ui/package.json index a6cdfaa47..f15be16be 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -28,6 +28,7 @@ "@supabase/supabase-js": "catalog:", "@tanstack/react-query": "catalog:", "@tanstack/react-table": "^8.21.3", + "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", "class-variance-authority": "^0.7.1", @@ -36,7 +37,7 @@ "next": "catalog:", "next-themes": "0.4.6", "prettier": "^3.8.1", - "react-day-picker": "^9.13.0", + "react-day-picker": "^9.13.2", "react-hook-form": "catalog:", "react-i18next": "catalog:", "sonner": "^2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f644738d..af394c896 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,20 +34,20 @@ catalogs: specifier: 4.1.18 version: 4.1.18 '@tanstack/react-query': - specifier: 5.90.20 - version: 5.90.20 + specifier: 5.90.21 + version: 5.90.21 '@types/eslint': specifier: 9.6.1 version: 9.6.1 '@types/node': - specifier: 25.2.1 - version: 25.2.1 + specifier: 25.2.3 + version: 25.2.3 '@types/nodemailer': specifier: 7.0.9 version: 7.0.9 '@types/react': - specifier: 19.2.13 - version: 19.2.13 + specifier: 19.2.14 + version: 19.2.14 '@types/react-dom': specifier: 19.2.3 version: 19.2.3 @@ -58,11 +58,11 @@ catalogs: specifier: 16.1.6 version: 16.1.6 eslint-config-turbo: - specifier: 2.8.3 - version: 2.8.3 + specifier: 2.8.5 + version: 2.8.5 i18next: - specifier: 25.8.4 - version: 25.8.4 + specifier: 25.8.5 + version: 25.8.5 i18next-browser-languagedetector: specifier: 8.2.0 version: 8.2.0 @@ -76,8 +76,11 @@ catalogs: specifier: 16.1.6 version: 16.1.6 nodemailer: - specifier: 8.0.0 - version: 8.0.0 + specifier: 8.0.1 + version: 8.0.1 + pino: + specifier: 10.3.1 + version: 10.3.1 react: specifier: 19.2.4 version: 19.2.4 @@ -94,11 +97,14 @@ catalogs: specifier: 20.3.1 version: 20.3.1 supabase: - specifier: 2.75.5 - version: 2.75.5 + specifier: 2.76.7 + version: 2.76.7 tailwindcss: specifier: 4.1.18 version: 4.1.18 + tsup: + specifier: 8.5.1 + version: 8.5.1 tw-animate-css: specifier: 1.4.0 version: 1.4.0 @@ -115,7 +121,7 @@ importers: version: 0.25.1 '@turbo/gen': specifier: ^2.7.6 - version: 2.8.3(@types/node@25.2.1)(typescript@5.9.3) + version: 2.8.5(@types/node@25.2.3) cross-env: specifier: ^10.0.0 version: 10.1.0 @@ -123,8 +129,8 @@ importers: specifier: ^3.8.1 version: 3.8.1 turbo: - specifier: 2.7.6 - version: 2.7.6 + specifier: 2.8.5 + version: 2.8.5 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -133,22 +139,22 @@ importers: dependencies: '@faker-js/faker': specifier: ^10.2.0 - version: 10.2.0 + version: 10.3.0 '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.71.1(react@19.2.4)) '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) lucide-react: specifier: 'catalog:' version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nodemailer: specifier: 'catalog:' - version: 8.0.0 + version: 8.0.1 react: specifier: 'catalog:' version: 19.2.4 @@ -188,16 +194,16 @@ importers: version: 4.1.18 '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/nodemailer': specifier: 'catalog:' version: 7.0.9 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) babel-plugin-react-compiler: specifier: 1.0.0 version: 1.0.0 @@ -226,14 +232,14 @@ importers: apps/e2e: devDependencies: '@playwright/test': - specifier: ^1.58.1 - version: 1.58.1 + specifier: ^1.58.2 + version: 1.58.2 '@supabase/supabase-js': specifier: 'catalog:' version: 2.95.3 '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 dotenv: specifier: 17.2.4 version: 17.2.4 @@ -248,7 +254,7 @@ importers: dependencies: '@edge-csrf/nextjs': specifier: 2.5.3-cloudflare-rc1 - version: 2.5.3-cloudflare-rc1(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 2.5.3-cloudflare-rc1(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.71.1(react@19.2.4)) @@ -311,13 +317,13 @@ importers: version: 0.0.10(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.5 - version: 1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.20(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.21(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) '@marsidev/react-turnstile': specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@nosecone/next': specifier: 1.1.0 - version: 1.1.0(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 1.1.0(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.2.4) @@ -326,7 +332,7 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -338,10 +344,10 @@ importers: version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + version: 4.2.3(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -356,7 +362,7 @@ importers: version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) recharts: specifier: 2.15.3 version: 2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -390,13 +396,13 @@ importers: version: 4.1.18 '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) babel-plugin-react-compiler: specifier: 1.0.0 version: 1.0.0 @@ -411,7 +417,7 @@ importers: version: 3.8.1 supabase: specifier: 'catalog:' - version: 2.75.5 + version: 2.76.7 tailwindcss: specifier: 'catalog:' version: 4.1.18 @@ -432,7 +438,7 @@ importers: version: link:../../tooling/typescript '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 packages/billing/core: devDependencies: @@ -492,7 +498,7 @@ importers: version: 2.95.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -501,7 +507,7 @@ importers: version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -510,7 +516,7 @@ importers: version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) zod: specifier: 3.25.76 version: 3.25.76 @@ -544,10 +550,10 @@ importers: version: link:../../ui '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -565,7 +571,7 @@ importers: version: 8.7.0 stripe: specifier: 'catalog:' - version: 20.3.1(@types/node@25.2.1) + version: 20.3.1(@types/node@25.2.3) devDependencies: '@kit/billing': specifier: workspace:* @@ -590,13 +596,13 @@ importers: version: link:../../ui '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 date-fns: specifier: ^4.1.0 version: 4.1.0 next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -629,19 +635,19 @@ importers: version: link:../wordpress '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 packages/cms/keystatic: dependencies: '@keystatic/core': specifier: 0.5.48 - version: 0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@keystatic/next': specifier: ^5.0.4 - version: 5.0.4(@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 5.0.4(@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@markdoc/markdoc': specifier: ^0.5.4 - version: 0.5.4(@types/react@19.2.13)(react@19.2.4) + version: 0.5.4(@types/react@19.2.14)(react@19.2.4) devDependencies: '@kit/cms-types': specifier: workspace:* @@ -660,10 +666,10 @@ importers: version: link:../../ui '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 react: specifier: 'catalog:' version: 19.2.4 @@ -702,10 +708,10 @@ importers: version: link:../../ui '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 wp-types: specifier: ^4.69.0 version: 4.69.0 @@ -763,7 +769,16 @@ importers: version: link:../../tooling/typescript '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 + '@types/react': + specifier: 'catalog:' + version: 19.2.14 + react: + specifier: 'catalog:' + version: 19.2.4 + react-dom: + specifier: 'catalog:' + version: 19.2.4(react@19.2.4) packages/features/accounts: dependencies: @@ -818,19 +833,19 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) lucide-react: specifier: 'catalog:' version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -845,7 +860,7 @@ importers: version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) zod: specifier: 3.25.76 version: 3.25.76 @@ -881,25 +896,25 @@ importers: version: 0.0.10(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.5 - version: 1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.20(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.21(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) '@supabase/supabase-js': specifier: 'catalog:' version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 lucide-react: specifier: 'catalog:' version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -947,25 +962,25 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 lucide-react: specifier: 'catalog:' version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-hook-form: specifier: 'catalog:' version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -977,7 +992,7 @@ importers: dependencies: '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 devDependencies: '@kit/eslint-config': specifier: workspace:* @@ -999,10 +1014,10 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 lucide-react: specifier: 'catalog:' version: 0.563.0(react@19.2.4) @@ -1014,7 +1029,7 @@ importers: version: 19.2.4(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) packages/features/team-accounts: dependencies: @@ -1072,16 +1087,16 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1093,7 +1108,7 @@ importers: version: 0.563.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1105,7 +1120,7 @@ importers: version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) zod: specifier: 3.25.76 version: 3.25.76 @@ -1114,7 +1129,7 @@ importers: dependencies: i18next: specifier: 'catalog:' - version: 25.8.4(typescript@5.9.3) + version: 25.8.5(typescript@5.9.3) i18next-browser-languagedetector: specifier: 'catalog:' version: 8.2.0 @@ -1136,10 +1151,10 @@ importers: version: link:../../tooling/typescript '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1148,7 +1163,7 @@ importers: version: 19.2.4(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) packages/mailers/core: devDependencies: @@ -1175,7 +1190,7 @@ importers: version: link:../../../tooling/typescript '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 zod: specifier: 3.25.76 version: 3.25.76 @@ -1184,7 +1199,7 @@ importers: dependencies: nodemailer: specifier: 'catalog:' - version: 8.0.0 + version: 8.0.1 devDependencies: '@kit/eslint-config': specifier: workspace:* @@ -1221,7 +1236,7 @@ importers: version: link:../../../tooling/typescript '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 zod: specifier: 3.25.76 version: 3.25.76 @@ -1243,6 +1258,9 @@ importers: packages/mcp-server: devDependencies: + '@kit/email-templates': + specifier: workspace:* + version: link:../email-templates '@kit/prettier-config': specifier: workspace:* version: link:../../tooling/prettier @@ -1254,10 +1272,19 @@ importers: version: 1.26.0(zod@3.25.76) '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 postgres: specifier: 3.4.8 version: 3.4.8 + tsup: + specifier: 'catalog:' + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0) zod: specifier: 3.25.76 version: 3.25.76 @@ -1284,7 +1311,7 @@ importers: version: link:../../../tooling/typescript '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 react: specifier: 'catalog:' version: 19.2.4 @@ -1305,7 +1332,7 @@ importers: version: link:../../../tooling/typescript '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 react: specifier: 'catalog:' version: 19.2.4 @@ -1314,7 +1341,7 @@ importers: dependencies: '@sentry/nextjs': specifier: 'catalog:' - version: 10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.102.1) + version: 10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.105.1) devDependencies: '@kit/eslint-config': specifier: workspace:* @@ -1330,7 +1357,7 @@ importers: version: link:../../../tooling/typescript '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 react: specifier: 'catalog:' version: 19.2.4 @@ -1360,10 +1387,10 @@ importers: version: 2.95.3 '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) zod: specifier: 3.25.76 version: 3.25.76 @@ -1408,10 +1435,10 @@ importers: version: 2.95.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) react: specifier: 'catalog:' version: 19.2.4 @@ -1446,8 +1473,8 @@ importers: packages/shared: dependencies: pino: - specifier: ^10.3.0 - version: 10.3.0 + specifier: 'catalog:' + version: 10.3.1 devDependencies: '@kit/eslint-config': specifier: workspace:* @@ -1458,9 +1485,12 @@ importers: '@kit/tsconfig': specifier: workspace:* version: link:../../tooling/typescript + '@types/node': + specifier: 'catalog:' + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 packages/supabase: dependencies: @@ -1485,16 +1515,16 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@types/node': specifier: 'catalog:' - version: 25.2.1 + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1515,7 +1545,7 @@ importers: version: 2.1.1 cmdk: specifier: 1.1.1 - version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) input-otp: specifier: 1.4.2 version: 1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1524,10 +1554,10 @@ importers: version: 0.563.0(react@19.2.4) radix-ui: specifier: 1.4.3 - version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-dropzone: specifier: ^14.4.0 - version: 14.4.0(react@19.2.4) + version: 14.4.1(react@19.2.4) react-top-loading-bar: specifier: 3.0.2 version: 3.0.2(react@19.2.4) @@ -1552,16 +1582,19 @@ importers: version: 2.95.3 '@tanstack/react-query': specifier: 'catalog:' - version: 5.90.20(react@19.2.4) + version: 5.90.21(react@19.2.4) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/node': + specifier: 'catalog:' + version: 25.2.3 '@types/react': specifier: 'catalog:' - version: 19.2.13 + version: 19.2.14 '@types/react-dom': specifier: 'catalog:' - version: 19.2.3(@types/react@19.2.13) + version: 19.2.3(@types/react@19.2.14) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1573,7 +1606,7 @@ importers: version: 9.39.2(jiti@2.6.1) next: specifier: 'catalog:' - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1581,14 +1614,14 @@ importers: specifier: ^3.8.1 version: 3.8.1 react-day-picker: - specifier: ^9.13.0 - version: 9.13.0(react@19.2.4) + specifier: ^9.13.2 + version: 9.13.2(react@19.2.4) react-hook-form: specifier: 'catalog:' version: 7.71.1(react@19.2.4) react-i18next: specifier: 'catalog:' - version: 16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1615,10 +1648,10 @@ importers: version: 9.6.1 eslint-config-next: specifier: 'catalog:' - version: 16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint-config-turbo: specifier: 'catalog:' - version: 2.8.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.7.6) + version: 2.8.5(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.5) devDependencies: '@kit/prettier-config': specifier: workspace:* @@ -1670,10 +1703,6 @@ packages: '@apm-js-collab/tracing-hooks@0.3.1': resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -1686,10 +1715,6 @@ packages: resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -1702,10 +1727,6 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} @@ -1732,11 +1753,6 @@ packages: resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.0': resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} @@ -1746,34 +1762,18 @@ packages: resolution: {integrity: sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -1784,10 +1784,6 @@ packages: '@corex/deepmerge@4.0.43': resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} @@ -1846,6 +1842,162 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1884,18 +2036,18 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@faker-js/faker@10.2.0': - resolution: {integrity: sha512-rTXwAsIxpCqzUnZvrxVh3L0QA0NzToqWBLAhV+zDV3MIIwiQhAZHMdPCIaj5n/yADu/tyk12wIPgL6YHGXJP+g==} + '@faker-js/faker@10.3.0': + resolution: {integrity: sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} - '@floating-ui/dom@1.7.4': - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} - '@floating-ui/react-dom@2.1.6': - resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + '@floating-ui/react-dom@2.1.7': + resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -2093,6 +2245,55 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -2102,8 +2303,84 @@ packages: '@types/node': optional: true - '@internationalized/date@3.10.0': - resolution: {integrity: sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@internationalized/date@3.11.0': + resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==} '@internationalized/message@3.1.8': resolution: {integrity: sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==} @@ -2141,9 +2418,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} @@ -2532,8 +2806,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.58.1': - resolution: {integrity: sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} hasBin: true @@ -2545,8 +2819,8 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} '@polka/url@1.0.0-next.29': @@ -3274,116 +3548,116 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-aria/actiongroup@3.7.21': - resolution: {integrity: sha512-OKcOOTCnbTGj/ItFH8p3O/2mK/Dbv1Y0PfmWku1jxXv20HaHz3DnU4jjFzZJZBDT4rYBFCJw+f9DhxmnA8hIcA==} + '@react-aria/actiongroup@3.7.23': + resolution: {integrity: sha512-CQyswtH0CWCJPYlAz39TUS3H0eA1Xplx4GalF71BYcFG6U6mbawgtiUHPNj9sXdUSwY3Pb2K2FBk3cEtk/fySQ==} 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 - '@react-aria/breadcrumbs@3.5.29': - resolution: {integrity: sha512-rKS0dryllaZJqrr3f/EAf2liz8CBEfmL5XACj+Z1TAig6GIYe1QuA3BtkX0cV9OkMugXdX8e3cbA7nD10ORRqg==} + '@react-aria/breadcrumbs@3.5.31': + resolution: {integrity: sha512-j8F2NMHFGT/n3alfFKdO4bvrY/ymtdL04GdclY7Vc6zOmCnWoEZ2UA0sFuV7Rk9dOL8fAtYV1kMD1ZRO/EMcGA==} 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 - '@react-aria/button@3.14.2': - resolution: {integrity: sha512-VbLIA+Kd6f/MDjd+TJBUg2+vNDw66pnvsj2E4RLomjI9dfBuN7d+Yo2UnsqKVyhePjCUZ6xxa2yDuD63IOSIYA==} + '@react-aria/button@3.14.4': + resolution: {integrity: sha512-6mTPiSSQhELnWlnYJ1Tm1B0VL1GGKAs2PGAY3ZGbPGQPPDc6Wu82yIhuAO8TTFJrXkwAiqjQawgDLil/yB0V7Q==} 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 - '@react-aria/calendar@3.9.2': - resolution: {integrity: sha512-uSLxLgOPRnEU4Jg59lAhUVA+uDx/55NBg4lpfsP2ynazyiJ5LCXmYceJi+VuOqMml7d9W0dB87OldOeLdIxYVA==} + '@react-aria/calendar@3.9.4': + resolution: {integrity: sha512-0BvU8cj6uHn622Vp8Xd21XxXtvp3Bh4Yk1pHloqDNmUvvdBN+ol3Xsm5gG3XKKkZ+6CCEi6asCbLaEg3SZSbyg==} 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 - '@react-aria/checkbox@3.16.2': - resolution: {integrity: sha512-29Mj9ZqXioJ0bcMnNGooHztnTau5pikZqX3qCRj5bYR3by/ZFFavYoMroh9F7s/MbFm/tsKX+Sf02lYFEdXRjA==} + '@react-aria/checkbox@3.16.4': + resolution: {integrity: sha512-FcZj6/f27mNp2+G5yxyOMRZbZQjJ1cuWvo0PPnnZ4ybSPUmSzI4uUZBk1wvsJVP9F9n+J2hZuYVCaN8pyzLweA==} 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 - '@react-aria/combobox@3.14.0': - resolution: {integrity: sha512-z4ro0Hma//p4nL2IJx5iUa7NwxeXbzSoZ0se5uTYjG1rUUMszg+wqQh/AQoL+eiULn7rs18JY9wwNbVIkRNKWA==} + '@react-aria/combobox@3.14.2': + resolution: {integrity: sha512-qwBeb8cMgK3xwrvXYHPtcphduD/k+oTcU18JHPvEO2kmR32knB33H81C2/Zoh4x86zTDJXaEtPscXBWuQ/M7AQ==} 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 - '@react-aria/datepicker@3.15.2': - resolution: {integrity: sha512-th078hyNqPf4P2K10su/y32zPDjs3lOYVdHvsL9/+5K1dnTvLHCK5vgUyLuyn8FchhF7cmHV49D+LZVv65PEpQ==} + '@react-aria/datepicker@3.16.0': + resolution: {integrity: sha512-QynYHIHE+wvuGopl/k05tphmDpykpfZ3l3eKnUfGrqvAYJEeCOyS0qoMlw7Vq3NscMLFbJI6ajqBmlmtgFNiSA==} 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 - '@react-aria/dialog@3.5.31': - resolution: {integrity: sha512-inxQMyrzX0UBW9Mhraq0nZ4HjHdygQvllzloT1E/RlDd61lr3RbmJR6pLsrbKOTtSvDIBJpCso1xEdHCFNmA0Q==} + '@react-aria/dialog@3.5.33': + resolution: {integrity: sha512-C5FpLAMJU6gQU8gztWKlEJ2A0k/JKl0YijNOv3Lizk+vUdF5njROSrmFs16bY5Hd6ycmsK9x/Pqkq3m/OpNFXA==} 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 - '@react-aria/dnd@3.11.3': - resolution: {integrity: sha512-MyTziciik1Owz3rqDghu0K3ZtTFvmj/R2ZsLDwbU9N4hKqGX/BKnrI8SytTn8RDqVv5LmA/GhApLngiupTAsXw==} + '@react-aria/dnd@3.11.5': + resolution: {integrity: sha512-3IGrABfK8Cf6/b/uEmGEDGeubWKMUK3umWunF/tdkWBnIaxpdj4gRkWFMw7siWQYnqir6AN567nrWXtHFcLKsA==} 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 - '@react-aria/focus@3.21.2': - resolution: {integrity: sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==} + '@react-aria/focus@3.21.4': + resolution: {integrity: sha512-6gz+j9ip0/vFRTKJMl3R30MHopn4i19HqqLfSQfElxJD+r9hBnYG1Q6Wd/kl/WRR1+CALn2F+rn06jUnf5sT8Q==} 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 - '@react-aria/form@3.1.2': - resolution: {integrity: sha512-R3i7L7Ci61PqZQvOrnL9xJeWEbh28UkTVgkj72EvBBn39y4h7ReH++0stv7rRs8p5ozETSKezBbGfu4UsBewWw==} + '@react-aria/form@3.1.4': + resolution: {integrity: sha512-GjPS85cE/34zal3vs6MOi7FxUsXwbxN4y6l1LFor2g92UK97gVobp238f3xdMW2T8IuaWGcnHeYFg+cjiZ51pQ==} 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 - '@react-aria/grid@3.14.5': - resolution: {integrity: sha512-XHw6rgjlTqc85e3zjsWo3U0EVwjN5MOYtrolCKc/lc2ItNdcY3OlMhpsU9+6jHwg/U3VCSWkGvwAz9hg7krd8Q==} + '@react-aria/grid@3.14.7': + resolution: {integrity: sha512-8eaJThNHUs75Xf4+FQC2NKQtTOVYkkDdA8VbfbqG06oYDAn7ETb1yhbwoqh1jOv7MezCNkYjyFe4ADsz2rBVcw==} 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 - '@react-aria/gridlist@3.14.1': - resolution: {integrity: sha512-keS03Am07aOn7RuNaRsMOyh0jscyhDn95asCVy4lxhl9A9TFk1Jw0o2L6q6cWRj1gFiKeacj/otG5H8ZKQQ2Wg==} + '@react-aria/gridlist@3.14.3': + resolution: {integrity: sha512-t3nr29nU5jRG9MdWe9aiMd02V8o0pmidLU/7c4muWAu7hEH+IYdeDthGDdXL9tXAom/oQ+6yt6sOfLxpsVNmGA==} 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 - '@react-aria/i18n@3.12.13': - resolution: {integrity: sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==} + '@react-aria/i18n@3.12.15': + resolution: {integrity: sha512-3CrAN7ORVHrckvTmbPq76jFZabqq+rScosGT5+ElircJ5rF5+JcdT99Hp5Xg6R10jk74e8G3xiqdYsUd+7iJMA==} 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 - '@react-aria/interactions@3.25.6': - resolution: {integrity: sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==} + '@react-aria/interactions@3.27.0': + resolution: {integrity: sha512-D27pOy+0jIfHK60BB26AgqjjRFOYdvVSkwC31b2LicIzRCSPOSP06V4gMHuGmkhNTF4+YWDi1HHYjxIvMeiSlA==} 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 - '@react-aria/label@3.7.22': - resolution: {integrity: sha512-jLquJeA5ZNqDT64UpTc9XJ7kQYltUlNcgxZ37/v4mHe0UZ7QohCKdKQhXHONb0h2jjNUpp2HOZI8J9++jOpzxA==} + '@react-aria/label@3.7.24': + resolution: {integrity: sha512-lcJbUy6xyicWKNgzfrXksrJ2CeCST2rDxGAvHOmUxSbFOm26kK710DjaFvtO4tICWh/TKW5mC3sm77soNcVUGA==} 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 - '@react-aria/landmark@3.0.7': - resolution: {integrity: sha512-t8c610b8hPLS6Vwv+rbuSyljZosI1s5+Tosfa0Fk4q7d+Ex6Yj7hLfUFy59GxZAufhUYfGX396fT0gPqAbU1tg==} + '@react-aria/landmark@3.0.9': + resolution: {integrity: sha512-YYyluDBCXupnMh91ccE5g27fczjYmzPebHqTkVYjH4B6k45pOoqsMmWBCMnOTl0qOCeioI+daT8W0MamAZzoSw==} 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 - '@react-aria/link@3.8.6': - resolution: {integrity: sha512-7F7UDJnwbU9IjfoAdl6f3Hho5/WB7rwcydUOjUux0p7YVWh/fTjIFjfAGyIir7MJhPapun1D0t97QQ3+8jXVcg==} + '@react-aria/link@3.8.8': + resolution: {integrity: sha512-hxQEvo5rrn2C0GOSwB/tROe+y//dyhmyXGbm8arDy6WF5Mj0wcjjrAu0/dhGYBqoltJa16iIEvs52xgzOC+f+Q==} 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 - '@react-aria/listbox@3.15.0': - resolution: {integrity: sha512-Ub1Wu79R9sgxM7h4HeEdjOgOKDHwduvYcnDqsSddGXgpkL8ADjsy2YUQ0hHY5VnzA4BxK36bLp4mzSna8Qvj1w==} + '@react-aria/listbox@3.15.2': + resolution: {integrity: sha512-xcrgSediV8MaVmsuDrDPmWywF82/HOv+H+Y/dgr6GLCWl0XDj5Q7PyAhDzUsYdZNIne3B9muGh6IQc3HdkgWqg==} 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 @@ -3391,68 +3665,68 @@ packages: '@react-aria/live-announcer@3.4.4': resolution: {integrity: sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==} - '@react-aria/menu@3.19.3': - resolution: {integrity: sha512-52fh8y8b2776R2VrfZPpUBJYC9oTP7XDy+zZuZTxPEd7Ywk0JNUl5F92y6ru22yPkS13sdhrNM/Op+V/KulmAg==} + '@react-aria/menu@3.20.0': + resolution: {integrity: sha512-BAsHuf7kTVmawNUkTUd5RB3ZvL6DQQT7hgZ2cYKd/1ZwYq4KO2wWGYdzyTOtK1qimZL0eyHyQwDYv4dNKBH4gw==} 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 - '@react-aria/meter@3.4.27': - resolution: {integrity: sha512-andOOdJkgRJF9vBi5VWRmFodK+GT+5X1lLeNUmb4qOX8/MVfX/RbK72LDeIhd7xC7rSCFHj3WvZ198rK4q0k3w==} + '@react-aria/meter@3.4.29': + resolution: {integrity: sha512-XAhJf8LlYQl+QQXqtpWvzjlrT8MZKEG6c8N3apC5DONgSKlCwfmDm4laGEJPqtuz3QGiOopsfSfyTFYHjWsfZw==} 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 - '@react-aria/numberfield@3.12.2': - resolution: {integrity: sha512-M2b+z0HIXiXpGAWOQkO2kpIjaLNUXJ5Q3/GMa3Fkr+B1piFX0VuOynYrtddKVrmXCe+r5t+XcGb0KS29uqv7nQ==} + '@react-aria/numberfield@3.12.4': + resolution: {integrity: sha512-TgKBjKOjyURzbqNR2wF4tSFmQKNK5DqE4QZSlQxpYYo1T6zuztkh+oTOUZ4IWCJymL5qLtuPfGHCZbR7B+DN2w==} 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 - '@react-aria/overlays@3.30.0': - resolution: {integrity: sha512-UpjqSjYZx5FAhceWCRVsW6fX1sEwya1fQ/TKkL53FAlLFR8QKuoKqFlmiL43YUFTcGK3UdEOy3cWTleLQwdSmQ==} + '@react-aria/overlays@3.31.1': + resolution: {integrity: sha512-U5BedzcXU97U5PWm4kIPnNoVpAs9KjTYfbkGx33vapmTVpGYhQyYW9eg6zW2E8ZKsyFJtQ/jkQnbWGen97aHSQ==} 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 - '@react-aria/progress@3.4.27': - resolution: {integrity: sha512-0OA1shs1575g1zmO8+rWozdbTnxThFFhOfuoL1m7UV5Dley6FHpueoKB1ECv7B+Qm4dQt6DoEqLg7wsbbQDhmg==} + '@react-aria/progress@3.4.29': + resolution: {integrity: sha512-orSaaFLX5LdD9UyxgBrmP1J/ivyEFX+5v4ENPQM5RH5+Hl+0OJa+8ozI0AfVKBqCYc89BOZfG7kzi7wFHACZcQ==} 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 - '@react-aria/radio@3.12.2': - resolution: {integrity: sha512-I11f6I90neCh56rT/6ieAs3XyDKvEfbj/QmbU5cX3p+SJpRRPN0vxQi5D1hkh0uxDpeClxygSr31NmZsd4sqfg==} + '@react-aria/radio@3.12.4': + resolution: {integrity: sha512-2sjBAE8++EtAAfjwPdrqEVswbzR4Mvcy4n8SvwUxTo02yESa9nolBzCSdAUFUmhrNj3MiMA+zLxQ+KACfUjJOg==} 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 - '@react-aria/searchfield@3.8.9': - resolution: {integrity: sha512-Yt2pj8Wb5/XsUr2T0DQqFv+DlFpzzWIWnNr9cJATUcWV/xw6ok7YFEg9+7EHtBmsCQxFFJtock1QfZzBw6qLtQ==} + '@react-aria/searchfield@3.8.11': + resolution: {integrity: sha512-5R0prEC+jRFwPeJsK6G4RN8QG3V/+EaIuw9p79G1gFD+1dY81ZakiZIIJaLWRyO7AzYBGyC/QFHtz0m3KGQT/Q==} 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 - '@react-aria/select@3.17.0': - resolution: {integrity: sha512-q5ZuyAn5jSOeI0Ys99951TaGcF4O7u1SSBVxPMwVVXOU8ZhToCNx+WG3n/JDYHEjqdo7sbsVRaPA7LkBzBGf5w==} + '@react-aria/select@3.17.2': + resolution: {integrity: sha512-oMpHStyMluRf67qxrzH5Qfcvw6ETQgZT1Qw2xvAxQVRd5IBb0PfzZS7TGiULOcMLqXAUOC28O/ycUGrGRKLarg==} 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 - '@react-aria/selection@3.26.0': - resolution: {integrity: sha512-ZBH3EfWZ+RfhTj01dH8L17uT7iNbXWS8u77/fUpHgtrm0pwNVhx0TYVnLU1YpazQ/3WVpvWhmBB8sWwD1FlD/g==} + '@react-aria/selection@3.27.1': + resolution: {integrity: sha512-8WQ4AtWiBnk9UEeYkqpH12dd8KQW2aFbNZvM4sDfLtz7K7HWyY/MkqMe/snk9IcoSa7t4zr0bnoZJcWSGgn2PQ==} 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 - '@react-aria/separator@3.4.13': - resolution: {integrity: sha512-0NlcrdBfQbcjWEXdHl3+uSY1272n2ljT1gWL2RIf6aQsQWTZ0gz0rTgRHy0MTXN+y+tICItUERJT4vmTLtIzVg==} + '@react-aria/separator@3.4.15': + resolution: {integrity: sha512-A1aPQhCaE8XeelNJYPjHtA2uh921ROh8PNiZI4o62x80wcziRoctN5PAtNHJAx7VKvX66A8ZVGbOqb7iqS3J5Q==} 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 - '@react-aria/spinbutton@3.6.19': - resolution: {integrity: sha512-xOIXegDpts9t3RSHdIN0iYQpdts0FZ3LbpYJIYVvdEHo9OpDS+ElnDzCGtwZLguvZlwc5s1LAKuKopDUsAEMkw==} + '@react-aria/spinbutton@3.7.1': + resolution: {integrity: sha512-Nisah6yzxOC6983u/5ck0w+OQoa3sRKmpDvWpTEX0g2+ZIABOl8ttdSd65XKtxXmXHdK8X1zmrfeGOBfBR3sKA==} 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 @@ -3463,32 +3737,32 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-aria/switch@3.7.8': - resolution: {integrity: sha512-AfsUq1/YiuoprhcBUD9vDPyWaigAwctQNW1fMb8dROL+i/12B+Zekj8Ml+jbU69/kIVtfL0Jl7/0Bo9KK3X0xQ==} + '@react-aria/switch@3.7.10': + resolution: {integrity: sha512-j7nrYnqX6H9J8GuqD0kdMECUozeqxeG19A2nsvfaTx3//Q7RhgIR9fqhQdVHW/wgraTlEHNH6AhDzmomBg0TNw==} 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 - '@react-aria/table@3.17.8': - resolution: {integrity: sha512-bXiZoxTMbsqUJsYDhHPzKc3jw0HFJ/xMsJ49a0f7mp5r9zACxNLeIU0wJ4Uvx37dnYOHKzGliG+rj5l4sph7MA==} + '@react-aria/table@3.17.10': + resolution: {integrity: sha512-xdEeyOzuETkOfAHhZrX7HOIwMUsCUr4rbPvHqdcNqg7Ngla2ck9iulZNAyvOPfFwELuBEd2rz1I9TYRQ2OzSQQ==} 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 - '@react-aria/tabs@3.10.8': - resolution: {integrity: sha512-sPPJyTyoAqsBh76JinBAxStOcbjZvyWFYKpJ9Uqw+XT0ObshAPPFSGeh8DiQemPs02RwJdrfARPMhyqiX8t59A==} + '@react-aria/tabs@3.11.0': + resolution: {integrity: sha512-9Gwo118GHrMXSyteCZL1L/LHLVlGSYkhGgiTL3e/UgnYjHfEfDJVTkV2JikuE2O/4uig52gQRlq5E99axLeE9Q==} 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 - '@react-aria/tag@3.7.2': - resolution: {integrity: sha512-JV679P5r4DftbqyNBRt7Nw9mP7dxaKPfikjyQuvUoEOa06wBLbM/hU9RJUPRvqK+Un6lgBDAmXD9NNf4N2xpdw==} + '@react-aria/tag@3.8.0': + resolution: {integrity: sha512-sTV6uRKFIFU1aljKb0QjM6fPPnzBuitrbkkCUZCJ0w0RIX1JinZPh96NknNtjFwWmqoROjVNCq51EUd0Hh2SQw==} 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 - '@react-aria/textfield@3.18.2': - resolution: {integrity: sha512-G+lM8VYSor6g9Yptc6hLZ6BF+0cq0pYol1z6wdQUQgJN8tg4HPtzq75lsZtlCSIznL3amgRAxJtd0dUrsAnvaQ==} + '@react-aria/textfield@3.18.4': + resolution: {integrity: sha512-ts3Vdy2qNOzjCVeO+4RH8FSgTYN2USAMcYFeGbHOriCukVOrvgRsqcDniW7xaT60LgFdlWMJsCusvltSIyo6xw==} 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 @@ -3499,38 +3773,38 @@ packages: 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 - '@react-aria/toggle@3.12.2': - resolution: {integrity: sha512-g25XLYqJuJpt0/YoYz2Rab8ax+hBfbssllcEFh0v0jiwfk2gwTWfRU9KAZUvxIqbV8Nm8EBmrYychDpDcvW1kw==} + '@react-aria/toggle@3.12.4': + resolution: {integrity: sha512-yVcl8kEFLsV47aCA22EMPcd/KWoYqPIPSzoKjRD/iWmxcP6iGzSxDjdUgMQojNGY8Q6wL8lUxfRqKBjvl/uezQ==} 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 - '@react-aria/toolbar@3.0.0-beta.21': - resolution: {integrity: sha512-yRCk/GD8g+BhdDgxd3I0a0c8Ni4Wyo6ERzfSoBkPkwQ4X2E2nkopmraM9D0fXw4UcIr4bnmvADzkHXtBN0XrBg==} + '@react-aria/toolbar@3.0.0-beta.23': + resolution: {integrity: sha512-FzvNf2hWtjEwk8F2MBf4qSs6AAR/p2WFSws6kJ4f0SrWXl4wR9VDEwBEUQcIPbWCK2aUsyOjubCh55Cl4t3MoQ==} 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 - '@react-aria/tooltip@3.8.8': - resolution: {integrity: sha512-CmHUqtXtFWmG4AHMEr9hIVex+oscK6xcM2V47gq9ijNInxe3M6UBu/dBdkgGP/jYv9N7tzCAjTR8nNIHQXwvWw==} + '@react-aria/tooltip@3.9.1': + resolution: {integrity: sha512-mvEhqpvF4v/wj9zw3a8bsAEnySutGbxKXXt39s6WvF6dkVfaXfsmV9ahuMCHH//UGh/yidZGLrXX4YVdrgS8lA==} 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 - '@react-aria/utils@3.31.0': - resolution: {integrity: sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==} + '@react-aria/utils@3.33.0': + resolution: {integrity: sha512-yvz7CMH8d2VjwbSa5nGXqjU031tYhD8ddax95VzJsHSPyqHDEGfxul8RkhGV6oO7bVqZxVs6xY66NIgae+FHjw==} 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 - '@react-aria/virtualizer@4.1.10': - resolution: {integrity: sha512-s0xOFh602ybTWuDrV/i6fV7Pz7vYghsY7F/RpYL/5IX9qCZ5C1FWFePpVktQAZghnd3ljH8hS8DULPeDfVLCrg==} + '@react-aria/virtualizer@4.1.12': + resolution: {integrity: sha512-va0VAD28nq7rk1vHZvnkq591EbWuDKBwh2NzAEn+zz9JjMtpg4utcihNXECJ1DwMRkpaT6q+KpOE7dSdzTxPBQ==} 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 - '@react-aria/visually-hidden@3.8.28': - resolution: {integrity: sha512-KRRjbVVob2CeBidF24dzufMxBveEUtUu7IM+hpdZKB+gxVROoh4XRLPv9SFmaH89Z7D9To3QoykVZoWD0lan6Q==} + '@react-aria/visually-hidden@3.8.30': + resolution: {integrity: sha512-iY44USEU8sJy0NOJ/sTDn3YlspbhHuVG3nx2YYrzfmxbS3i+lNwkCfG8kJ77dtmbuDLIdBGKENjGkbcwz3kiJg==} 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 @@ -3694,107 +3968,107 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-stately/calendar@3.9.0': - resolution: {integrity: sha512-U5Nf2kx9gDhJRxdDUm5gjfyUlt/uUfOvM1vDW2UA62cA6+2k2cavMLc2wNlXOb/twFtl6p0joYKHG7T4xnEFkg==} + '@react-stately/calendar@3.9.2': + resolution: {integrity: sha512-AQj8/izwb7eY+KFqKcMLI2ygvnbAIwLuQG5KPHgJsMygFqnN4yzXKz5orGqVJnxEXLKiLPteVztx7b5EQobrtw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/checkbox@3.7.2': - resolution: {integrity: sha512-j1ycUVz5JmqhaL6mDZgDNZqBilOB8PBW096sDPFaTtuYreDx2HOd1igxiIvwlvPESZwsJP7FVM3mYnaoXtpKPA==} + '@react-stately/checkbox@3.7.4': + resolution: {integrity: sha512-oXHMkK22CWLcmNlunDuu4p52QXYmkpx6es9AjWx/xlh3XLZdJzo/5SANioOH1QvBtwPA/c2KQy+ZBqC21NtMHw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/collections@3.12.8': - resolution: {integrity: sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==} + '@react-stately/collections@3.12.9': + resolution: {integrity: sha512-2jywPMhVgMOh0XtutxPqIxFCIiLOnL/GXIrRKoBEo8M3Q24NoMRBavUrn9RTvjqNnec1i/8w1/8sq8cmCKEohA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/combobox@3.12.0': - resolution: {integrity: sha512-A6q9R/7cEa/qoQsBkdslXWvD7ztNLLQ9AhBhVN9QvzrmrH5B4ymUwcTU8lWl22ykH7RRwfonLeLXJL4C+/L2oQ==} + '@react-stately/combobox@3.12.2': + resolution: {integrity: sha512-h4YRmzA+s3aMwUrXm6jyWLN0BWWXUNiodArB1wC24xNdeI7S8O3mxz6G2r3Ne8AE02FXmZXs9SD30Mx5vVVuqQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/data@3.14.1': - resolution: {integrity: sha512-lDNc4gZ6kVZcrABeeQZPTTnP+1ykNylSvFzAC/Hq1fs8+s54xLRvoENWIyG+yK19N9TIGEoA0AOFG8PoAun43g==} + '@react-stately/data@3.15.1': + resolution: {integrity: sha512-lchubLxCWg1Yswpe9yRYJAjmzP0eTYZe+AQyFJQRIT6axRi9Gs92RIZ7zhwLXxI0vcWpnAWADB9kD4bsos7xww==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/datepicker@3.15.2': - resolution: {integrity: sha512-S5GL+W37chvV8knv9v0JRv0L6hKo732qqabCCHXzOpYxkLIkV4f/y3cHdEzFWzpZ0O0Gkg7WgeYo160xOdBKYg==} + '@react-stately/datepicker@3.16.0': + resolution: {integrity: sha512-mYtzKXufFVivrHjmxys3ryJFMPIQNhVqaSItmGnWv3ehxw+0HKBrROf3BFiEN4zP20euoP149ZaR4uNx90kMYw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/dnd@3.7.1': - resolution: {integrity: sha512-O1JBJ4HI1rVNKuoa5NXiC5FCrCEkr9KVBoKNlTZU8/cnQselhbEsUfMglAakO2EuwIaM1tIXoNF5J/N5P+6lTA==} + '@react-stately/dnd@3.7.3': + resolution: {integrity: sha512-yBtzAimyYvJWnzP80Scx7l559+43TVSyjaMpUR6/s2IjqD3XoPKgPsv7KaFUmygBTkCBGBFJn404rYgMCOsu3g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-stately/flags@3.1.2': resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} - '@react-stately/form@3.2.2': - resolution: {integrity: sha512-soAheOd7oaTO6eNs6LXnfn0tTqvOoe3zN9FvtIhhrErKz9XPc5sUmh3QWwR45+zKbitOi1HOjfA/gifKhZcfWw==} + '@react-stately/form@3.2.3': + resolution: {integrity: sha512-NPvjJtns1Pq9uvqeRJCf8HIdVmOm2ARLYQ2F/sqXj1w5IChJ4oWL4Xzvj29/zBitgE1vVjDhnrnwSfNlHZGX0g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/grid@3.11.6': - resolution: {integrity: sha512-vWPAkzpeTIsrurHfMubzMuqEw7vKzFhIJeEK5sEcLunyr1rlADwTzeWrHNbPMl66NAIAi70Dr1yNq+kahQyvMA==} + '@react-stately/grid@3.11.8': + resolution: {integrity: sha512-tCabR5U7ype+uEElS5Chv5n6ntUv3drXa9DwebjO05cFevUmjTkEfYPJWixpgX4UlCCvjdUFgzeQlJF+gCiozg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/layout@4.5.1': - resolution: {integrity: sha512-Zk92HM6a8KFdyPzslhLCOmrrsvJ28+vFBisgiKMwVhe96cWlax1m9i4ktmO43xaUpSZkn06DRD/2k0d1x+Uwjw==} + '@react-stately/layout@4.5.3': + resolution: {integrity: sha512-BDYnvO2AKzvWfxxVM96kif3qCynsA+XcNoQC+T77exH+LLT8zlK9oOdarZXTlok/eZmjs6+5wmjq51PeL6eM5w==} 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 - '@react-stately/list@3.13.1': - resolution: {integrity: sha512-eHaoauh21twbcl0kkwULhVJ+CzYcy1jUjMikNVMHOQdhr4WIBdExf7PmSgKHKqsSPhpGg6IpTCY2dUX3RycjDg==} + '@react-stately/list@3.13.3': + resolution: {integrity: sha512-xN0v7rzhIKshhcshOzx+ZgVngXnGCtMPRdhoDLGaHzQy5YfxvKBMNLCnr5Lm4T1U/kIvHbyzxmr5uwmH8WxoIg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/menu@3.9.8': - resolution: {integrity: sha512-bo0NOhofnTHLESiYfsSSw6gyXiPVJJ0UlN2igUXtJk5PmyhWjFzUzTzcnd7B028OB0si9w3LIWM3stqz5271Eg==} + '@react-stately/menu@3.9.10': + resolution: {integrity: sha512-dY9FzjQ+6iNInVujZPyMklDGoSbaoO0yguUnALAY+yfkPAyStEElfm4aXZgRfNKOTNHe9E34oV7qefSYsclvTg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/numberfield@3.10.2': - resolution: {integrity: sha512-jlKVFYaH3RX5KvQ7a+SAMQuPccZCzxLkeYkBE64u1Zvi7YhJ8hkTMHG/fmZMbk1rHlseE2wfBdk0Rlya3MvoNQ==} + '@react-stately/numberfield@3.10.4': + resolution: {integrity: sha512-EniHHwXOw/Ta0x5j61OvldDAvLoi/8xOo//bzrqwnDvf2/1IKGFMD9CHs7HYhQw+9oNl3Q2V1meOTNPc4PvoMQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/overlays@3.6.20': - resolution: {integrity: sha512-YAIe+uI8GUXX8F/0Pzr53YeC5c/bjqbzDFlV8NKfdlCPa6+Jp4B/IlYVjIooBj9+94QvbQdjylegvYWK/iPwlg==} + '@react-stately/overlays@3.6.22': + resolution: {integrity: sha512-sWBnuy5dqVp8d+1e+ABTRVB3YBcOW86/90pF5PWY44au3bUFXVSUBO2QMdR/6JtojDoPRmrjufonI19/Zs/20w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/radio@3.11.2': - resolution: {integrity: sha512-UM7L6AW+k8edhSBUEPZAqiWNRNadfOKK7BrCXyBiG79zTz0zPcXRR+N+gzkDn7EMSawDeyK1SHYUuoSltTactg==} + '@react-stately/radio@3.11.4': + resolution: {integrity: sha512-3svsW5VxJA5/p1vO+Qlxv+7Jq9g7f4rqX9Rbqdfd+pH7ykHaV0CUKkSRMaWfcY8Vgaf2xmcc6dvusPRqKX8T1A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/searchfield@3.5.16': - resolution: {integrity: sha512-MRfqT1lZ24r94GuFNcGJXsfijZoWjSMySCT60T6NXtbOzVPuAF3K+pL70Rayq/EWLJjS2NPHND11VTs0VdcE0Q==} + '@react-stately/searchfield@3.5.18': + resolution: {integrity: sha512-C3/1wOON5oK0QBljj0vSbHm/IWgd29NxB+7zT1JjZcxtbcFxCj4HOxKdnPCT/d8Pojb0YS26QgKzatLZ0NnhgQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/select@3.8.0': - resolution: {integrity: sha512-A721nlt0DSCDit0wKvhcrXFTG5Vv1qkEVkeKvobmETZy6piKvwh0aaN8iQno5AFuZaj1iOZeNjZ/20TsDJR/4A==} + '@react-stately/select@3.9.1': + resolution: {integrity: sha512-CJQRqv8Dg+0RRvcig3a2YfY6POJIscDINvidRF31yK6J72rsP01dY3ria9aJjizNDHR9Q5dWFp/z+ii0cOTWIQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/selection@3.20.6': - resolution: {integrity: sha512-a0bjuP2pJYPKEiedz2Us1W1aSz0iHRuyeQEdBOyL6Z6VUa6hIMq9H60kvseir2T85cOa4QggizuRV7mcO6bU5w==} + '@react-stately/selection@3.20.8': + resolution: {integrity: sha512-V1kRN1NLW+i/3Xv+Q0pN9OzuM0zFEW9mdXOOOq7l+YL6hFjqIjttT2/q4KoyiNV3W0hfoRFSTQ7XCgqnqtwEng==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/table@3.15.1': - resolution: {integrity: sha512-MhMAgE/LgAzHcAn1P3p/nQErzJ6DiixSJ1AOt2JlnAKEb5YJg4ATKWCb2IjBLwywt9ZCzfm3KMUzkctZqAoxwA==} + '@react-stately/table@3.15.3': + resolution: {integrity: sha512-W1wR0O/PmdD8hCUFIAelHICjUX/Ii6ZldPlH6EILr9olyGpoCaY7XmnyG7kii1aANuQGBeskjJdXvS6LX/gyDw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tabs@3.8.6': - resolution: {integrity: sha512-9RYxmgjVIxUpIsGKPIF7uRoHWOEz8muwaYiStCVeyiYBPmarvZoIYtTXcwSMN/vEs7heVN5uGCL6/bfdY4+WiA==} + '@react-stately/tabs@3.8.8': + resolution: {integrity: sha512-BZImWT+pHZitImRQkoL7jVhTtpGPSra1Rhh4pi8epzwogeqseEIEpuWpQebjQP74r1kfNi/iT2p5Qb31eWfh1Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -3803,159 +4077,159 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/toggle@3.9.2': - resolution: {integrity: sha512-dOxs9wrVXHUmA7lc8l+N9NbTJMAaXcYsnNGsMwfXIXQ3rdq+IjWGNYJ52UmNQyRYFcg0jrzRrU16TyGbNjOdNQ==} + '@react-stately/toggle@3.9.4': + resolution: {integrity: sha512-tjWsshRJtHC+PI5NYMlnDlV/BTo1eWq6fmR6x1mXlQfKuKGTJRzhgJyaQ2mc5K+LkifD7fchOhfapHCrRlzwMg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tooltip@3.5.8': - resolution: {integrity: sha512-gkcUx2ROhCiGNAYd2BaTejakXUUNLPnnoJ5+V/mN480pN+OrO8/2V9pqb/IQmpqxLsso93zkM3A4wFHHLBBmPQ==} + '@react-stately/tooltip@3.5.10': + resolution: {integrity: sha512-GauUdc6Of08Np2iUw4xx/DdgpvszS9CxJWYcRnNyAAGPLQrmniVrpJvb0EUKQTP9sUSci1SlmpvJh4SNZx26Bw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/tree@3.9.3': - resolution: {integrity: sha512-ZngG79nLFxE/GYmpwX6E/Rma2MMkzdoJPRI3iWk3dgqnGMMzpPnUp/cvjDsU3UHF7xDVusC5BT6pjWN0uxCIFQ==} + '@react-stately/tree@3.9.5': + resolution: {integrity: sha512-UpvBlzL/MpFdOepDg+cohI/zvw8DEVM8cXY/OZ8tKUXWpew1HpUglwnAI3ivm0L2k9laUIB9siW0g04ZWiH9Lg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/utils@3.10.8': - resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} + '@react-stately/utils@3.11.0': + resolution: {integrity: sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-stately/virtualizer@4.4.4': - resolution: {integrity: sha512-ri8giqXSZOrznZDCCOE4U36wSkOhy+hrFK7yo/YVcpxTqqp3d3eisfKMqbDsgqBW+XTHycTU/xeAf0u9NqrfpQ==} + '@react-stately/virtualizer@4.4.5': + resolution: {integrity: sha512-MP33zys3nRYTk/+3BPchxlil9GrwbMksc3XuvNACeZqYEA/oEidsHffgPL+LY0iitKCmQE6pg49MI5HvBuOd2w==} 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 - '@react-types/actionbar@3.1.19': - resolution: {integrity: sha512-1LrfA6xftrrn633kkbd5UVUo2RNDTFUwjSxlBJSo3n/4Q/mirl2mb5tTyl6/UI76IPFJEi6DQ+07VPA0tb4lbA==} + '@react-types/actionbar@3.1.20': + resolution: {integrity: sha512-K3fajms4FmeVnUAXuJ7iwhtS2Lh8hHFZCqCrvHdHqzRyIsx7vMRvITXyhHnIEuIXDCHb2k7cSzFJzn9kvJLosw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/actiongroup@3.4.21': - resolution: {integrity: sha512-7RkBITpUSsMnzzTpmNbMur0cgwW6tCEc5ChjkzFN12Uy8VTII74PP3i4HfyIbn3ufO5oRkVhw0CEeUrTtqWRJQ==} + '@react-types/actiongroup@3.4.22': + resolution: {integrity: sha512-J3MIpK3a+AXmyUTd2+bHn3J2XcZV1oJpZxFxmWonitiIvbyEZbiZHeMrBPxHST60BxVGzQK/J+mROZNjxIRWpg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/breadcrumbs@3.7.17': - resolution: {integrity: sha512-IhvVTcfli5o/UDlGACXxjlor2afGlMQA8pNR3faH0bBUay1Fmm3IWktVw9Xwmk+KraV2RTAg9e+E6p8DOQZfiw==} + '@react-types/breadcrumbs@3.7.18': + resolution: {integrity: sha512-zwltqx2XSELBRQeuCraxrdfT4fpIOVu6eQXsZ4RhWlsT7DLhzj3pUGkxdPDAMfYaVdyNBqc+nhiAnCwz6tUJ8A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/button@3.14.1': - resolution: {integrity: sha512-D8C4IEwKB7zEtiWYVJ3WE/5HDcWlze9mLWQ5hfsBfpePyWCgO3bT/+wjb/7pJvcAocrkXo90QrMm85LcpBtrpg==} + '@react-types/button@3.15.0': + resolution: {integrity: sha512-X/K2/Oeuq7Hi8nMIzx4/YlZuvWFiSOHZt27p4HmThCnNO/9IDFPmvPrpkYjWN5eN9Nuk+P5vZUb4A7QJgYpvGA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/calendar@3.8.0': - resolution: {integrity: sha512-ZDZgfZgbz1ydWOFs1mH7QFfX3ioJrmb3Y/lkoubQE0HWXLZzyYNvhhKyFJRS1QJ40IofLSBHriwbQb/tsUnGlw==} + '@react-types/calendar@3.8.2': + resolution: {integrity: sha512-QbPFhvBQfrsz3x1Nnatr5SL+8XtbxvP4obESFuDrKmsqaaAv+jG5vwLiPTKp6Z3L+MWkCvKavBPuW+byhq+69A==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/checkbox@3.10.2': - resolution: {integrity: sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==} + '@react-types/checkbox@3.10.3': + resolution: {integrity: sha512-Xw4jHG7uK352Wc18XXzdzmtr3Xjg8d2tPoBGNgsw39f92EY2UpoDAPHxYR0BaDe04lGfAn6YwVivI4OGVbjXIg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/combobox@3.13.9': - resolution: {integrity: sha512-G6GmLbzVkLW6VScxPAr/RtliEyPhBClfYaIllK1IZv+Z42SVnOpKzhnoe79BpmiFqy1AaC3+LjZX783mrsHCwA==} + '@react-types/combobox@3.13.11': + resolution: {integrity: sha512-5/tdmTAvqPpiWzEeaV7uLLSbSTkkoQ1mVz6NfKMPuw4ZBkY3lPc9JDkkQjY/JrquZao+KY4Dx8ZIoS0NqkrFrw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/datepicker@3.13.2': - resolution: {integrity: sha512-+M6UZxJnejYY8kz0spbY/hP08QJ5rsZ3aNarRQQHc48xV2oelFLX5MhAqizfLEsvyfb0JYrhWoh4z1xZtAmYCg==} + '@react-types/datepicker@3.13.4': + resolution: {integrity: sha512-B5sAPoYZfluDBpgVK3ADlHbXBKRkFCQFO18Bs091IvRRwqzfoO/uf+/9UpXMw+BEF4pciLf0/kdiVQTvI3MzlA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/dialog@3.5.22': - resolution: {integrity: sha512-smSvzOcqKE196rWk0oqJDnz+ox5JM5+OT0PmmJXiUD4q7P5g32O6W5Bg7hMIFUI9clBtngo8kLaX2iMg+GqAzg==} + '@react-types/dialog@3.5.23': + resolution: {integrity: sha512-3tMzweYuaDOaufF5tZPMgXSA0pPFJNgdg89YRITh0wMXMG0pm+tAKVQJL1TSLLhOiLCEL08V8M/AK67dBdr2IA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/grid@3.3.6': - resolution: {integrity: sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==} + '@react-types/grid@3.3.7': + resolution: {integrity: sha512-riET3xeKPTcRWQy6hYCMxdbdL3yubPY5Ow66b2GA2rEqoYvmDBniYXAM2Oh+q9s+YgnAP7qJK++ym8NljvHiLA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/link@3.6.5': - resolution: {integrity: sha512-+I2s3XWBEvLrzts0GnNeA84mUkwo+a7kLUWoaJkW0TOBDG7my95HFYxF9WnqKye7NgpOkCqz4s3oW96xPdIniQ==} + '@react-types/link@3.6.6': + resolution: {integrity: sha512-M6WXxUJFmiF6GNu7xUH0uHj0jsorFBN6npkfSCNM4puStC8NbUT2+ZPySQyZXCoHMQ89g6qZ6vCc8QduVkTE7Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/listbox@3.7.4': - resolution: {integrity: sha512-p4YEpTl/VQGrqVE8GIfqTS5LkT5jtjDTbVeZgrkPnX/fiPhsfbTPiZ6g0FNap4+aOGJFGEEZUv2q4vx+rCORww==} + '@react-types/listbox@3.7.5': + resolution: {integrity: sha512-Cn+yNip+YZBaGzu+z5xPNgmfSupnLl+li7uG5hRc+EArkk8/G42myRXz6M8wPrLM1bFAq3r85tAbyoXVmKG5Jw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/menu@3.10.5': - resolution: {integrity: sha512-HBTrKll2hm0VKJNM4ubIv1L9MNo8JuOnm2G3M+wXvb6EYIyDNxxJkhjsqsGpUXJdAOSkacHBDcNh2HsZABNX4A==} + '@react-types/menu@3.10.6': + resolution: {integrity: sha512-OJTznQ4xE/VddBJU+HO4x5tceSOdyQhiHA1bREE1aHl+PcgHOUZLdMjXp1zFaGF16HhItHJaxpifJ4hzf4hWQA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/meter@3.4.13': - resolution: {integrity: sha512-EiarfbpHcvmeyXvXcr6XLaHkNHuGc4g7fBVEiDPwssFJKKfbUzqnnknDxPjyspqUVRcXC08CokS98J1jYobqDg==} + '@react-types/meter@3.4.14': + resolution: {integrity: sha512-rNw0Do2AM3zLGZ0pSWweViuddg1uW99PWzE6RQXE8nsTHTeiwDZt9SYGdObEnjd+nJ3YzemqekG0Kqt93iNBcA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/numberfield@3.8.15': - resolution: {integrity: sha512-97r92D23GKCOjGIGMeW9nt+/KlfM3GeWH39Czcmd2/D5y3k6z4j0avbsfx2OttCtJszrnENjw3GraYGYI2KosQ==} + '@react-types/numberfield@3.8.17': + resolution: {integrity: sha512-Q9n24OaSMXrebMowbtowmHLNclknN3XkcBIaYMwA2BIGIl+fZFnI8MERM0pG87W+wki6FepDExsDW9YxQF4pnw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/overlays@3.9.2': - resolution: {integrity: sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==} + '@react-types/overlays@3.9.3': + resolution: {integrity: sha512-LzetThNNk8T26pQRbs1I7+isuFhdFYREy7wJCsZmbB0FnZgCukGTfOtThZWv+ry11veyVJiX68jfl4SV6ACTWA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/progress@3.5.16': - resolution: {integrity: sha512-I9tSdCFfvQ7gHJtm90VAKgwdTWXQgVNvLRStEc0z9h+bXBxdvZb+QuiRPERChwFQ9VkK4p4rDqaFo69nDqWkpw==} + '@react-types/progress@3.5.17': + resolution: {integrity: sha512-JtiGlek6QS04bFrRj1WfChjPNr7+3/+pd6yZayXGUkQUPHt1Z/cFnv3QZ/tSQTdUt1XXmjnCak9ZH9JQBqe64Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/radio@3.9.2': - resolution: {integrity: sha512-3UcJXu37JrTkRyP4GJPDBU7NmDTInrEdOe+bVzA1j4EegzdkJmLBkLg5cLDAbpiEHB+xIsvbJdx6dxeMuc+H3g==} + '@react-types/radio@3.9.3': + resolution: {integrity: sha512-w2BrMGIiZxYXPCnnB2NQyifwE/rRFMIW87MyawrKO9zPSbnDkqLIHAAtqmlNk2zkz1ZEWjk9opNsuztjP7D4sA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/searchfield@3.6.6': - resolution: {integrity: sha512-cl3itr/fk7wbIQc2Gz5Ie8aVeUmPjVX/mRGS5/EXlmzycAKNYTvqf2mlxwObLndtLISmt7IgNjRRhbUUDI8Ang==} + '@react-types/searchfield@3.6.7': + resolution: {integrity: sha512-POo3spZcYD14aqo0f4eNbymJ8w9EKrlu0pOOjYYWI2P0GUSRmib9cBA9xZFhvRGHuNlHo3ePjeFitYQI7L3g1g==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/select@3.11.0': - resolution: {integrity: sha512-SzIsMFVPCbXE1Z1TLfpdfiwJ1xnIkcL1/CjGilmUKkNk5uT7rYX1xCJqWCjXI0vAU1xM4Qn+T3n8de4fw6HRBg==} + '@react-types/select@3.12.1': + resolution: {integrity: sha512-PtIUymvQNIIzgr+piJtK/8gbH7akWtbswIbfoADPSxtZEd1/vfUIO0s8c750s3XYNlmx/4DrhugQsLYwgC35yg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/shared@3.32.1': - resolution: {integrity: sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==} + '@react-types/shared@3.33.0': + resolution: {integrity: sha512-xuUpP6MyuPmJtzNOqF5pzFUIHH2YogyOQfUQHag54PRmWB7AbjuGWBUv0l1UDmz6+AbzAYGmDVAzcRDOu2PFpw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/switch@3.5.15': - resolution: {integrity: sha512-r/ouGWQmIeHyYSP1e5luET+oiR7N7cLrAlWsrAfYRWHxqXOSNQloQnZJ3PLHrKFT02fsrQhx2rHaK2LfKeyN3A==} + '@react-types/switch@3.5.16': + resolution: {integrity: sha512-6fynclkyg0wGHo3f1bwk4Z+gZZEg0Z63iP5TFhgHWdZ8W+Uq6F3u7V4IgQpuJ2NleL1c2jy2/CKdS9v06ac2Og==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/table@3.13.4': - resolution: {integrity: sha512-I/DYiZQl6aNbMmjk90J9SOhkzVDZvyA3Vn3wMWCiajkMNjvubFhTfda5DDf2SgFP5l0Yh6TGGH5XumRv9LqL5Q==} + '@react-types/table@3.13.5': + resolution: {integrity: sha512-4/CixlNmXSuJuX2IKuUlgNd/dEgNh3WvfE/bdwuI1t5JBdShP9tHIzSkgZbrzE2xX46NeA2xq4vXNO5kBv+QDA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/tabs@3.3.19': - resolution: {integrity: sha512-fE+qI43yR5pAMpeqPxGqQq9jDHXEPqXskuxNHERMW0PYMdPyem2Cw6goc5F4qeZO3Hf6uPZgHkvJz2OAq7TbBw==} + '@react-types/tabs@3.3.21': + resolution: {integrity: sha512-Dq9bKI62rHoI4LGGcBGlZ5s0aSwB0G4Y8o0r7hQZvf1eZWc9fmqdAdTTaGG/RUyhMIGRYWl5RRUBUuC5RmaO6w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/textfield@3.12.6': - resolution: {integrity: sha512-hpEVKE+M3uUkTjw2WrX1NrH/B3rqDJFUa+ViNK2eVranLY4ZwFqbqaYXSzHupOF3ecSjJJv2C103JrwFvx6TPQ==} + '@react-types/textfield@3.12.7': + resolution: {integrity: sha512-ddiacsS6sLFtAn2/fym7lR8nbdsLgPfelNDcsDqHiu6XUHh5TCNe8ItXHFaIiyfnKTH8uJqZrSli4wfAYNfMsw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-types/tooltip@3.4.21': - resolution: {integrity: sha512-ugGHOZU6WbOdeTdbjnaEc+Ms7/WhsUCg+T3PCOIeOT9FG02Ce189yJ/+hd7oqL/tVwIhEMYJIqSCgSELFox+QA==} + '@react-types/tooltip@3.5.1': + resolution: {integrity: sha512-h6xOAWbWUJKs9CzcCyzSPATLHq7W5dS866HkXLrtCrRDShLuzQnojZnctD2tKtNt17990hjnOhl36GUBuO5kyw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 @@ -4124,16 +4398,16 @@ packages: resolution: {integrity: sha512-YWIkL6/dnaiQyFiZXJ/nN+NXGv/15z45ia86bE/TMq01CubX/DUOilgsFz0pk2v/pg3tp/U2MskLO9Hz0cnqeg==} engines: {node: '>=18'} - '@sentry/babel-plugin-component-annotate@4.9.0': - resolution: {integrity: sha512-TJ7sVoa2Bf36lpJjBAzpNDC5Hg+evjsQnqUPeDx9Nz/YFw0u9rK1cwvi95gVWpx7PJSDCkljIv3aw0m4RatHpQ==} + '@sentry/babel-plugin-component-annotate@4.9.1': + resolution: {integrity: sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==} engines: {node: '>= 14'} '@sentry/browser@10.38.0': resolution: {integrity: sha512-3phzp1YX4wcQr9mocGWKbjv0jwtuoDBv7+Y6Yfrys/kwyaL84mDLjjQhRf4gL5SX7JdYkhBp4WaiNlR0UC4kTA==} engines: {node: '>=18'} - '@sentry/bundler-plugin-core@4.9.0': - resolution: {integrity: sha512-gOVgHG5BrxCFmZow1XovlDr1FH/gO/LfD8OKci1rryeqHVBLr3+S4yS4ACl+E5lfQPym8Ve1BKh793d1rZ0dyA==} + '@sentry/bundler-plugin-core@4.9.1': + resolution: {integrity: sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ==} engines: {node: '>= 14'} '@sentry/cli-darwin@2.58.4': @@ -4234,8 +4508,8 @@ packages: resolution: {integrity: sha512-lElDFktj/PyRC/LDHejPFhQmHVMCB9Celj+IHi36aw96a/LekqF6/7vmp26hDtH58QtuiPO3h5voqEAMUOkSlw==} engines: {node: '>=18'} - '@sentry/webpack-plugin@4.9.0': - resolution: {integrity: sha512-2usiAS8vVBb24DXMYHtHsuCasnxo5uJMO6tpGPCMpyLYVooq5ypNvV+egiwlO6Dmyp9/BFK2hcK1vPRL5K5Trw==} + '@sentry/webpack-plugin@4.9.1': + resolution: {integrity: sha512-Ssx2lHiq8VWywUGd/hmW3U3VYBC0Up7D6UzUiDAWvy18PbTCVszaa54fKMFEQ1yIBg/ePRET53pIzfkcZgifmQ==} engines: {node: '>= 14'} peerDependencies: webpack: '>=4.40.0' @@ -4248,6 +4522,9 @@ packages: resolution: {integrity: sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==} engines: {node: '>=10'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -4294,8 +4571,8 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@swc/helpers@0.5.18': + resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} '@tailwindcss/node@4.1.18': resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} @@ -4388,8 +4665,8 @@ packages: '@tanstack/query-core@5.90.20': resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} - '@tanstack/react-query@5.90.20': - resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + '@tanstack/react-query@5.90.21': + resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} peerDependencies: react: ^18 || ^19 @@ -4436,29 +4713,20 @@ packages: peerDependencies: graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || 14 || 15 || 16 - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@turbo/gen@2.8.3': - resolution: {integrity: sha512-UQP6zMJPfnJKKFO7skLcTJWBFHG+XdMnXszliFXg/TEhd53CuVYmIXCN23n7UoMnxRBSsq0LB4sUM9JgsPewSQ==} + '@turbo/gen@2.8.5': + resolution: {integrity: sha512-FTscVSa8hn8x3NRyNXwKy0d+H2nE9RrJ/i//F3NyXIWvLwyvLJvyLjHgwrwp+6EeE/ptdCvign9svBZtVnH93Q==} hasBin: true - '@turbo/workspaces@2.8.3': - resolution: {integrity: sha512-p31wsDQ2DSd+TCld9xIqTG4pNA+we6/+AAZ9iCtpITRfUz3/iO8wzcz0fPfya3ostEj2B4UksLAXF570VAeO5w==} + '@turbo/workspaces@2.8.5': + resolution: {integrity: sha512-/w5+ujuGztJCwD9cTRPGY47oFRk6bT2jPeBvUjA+3/1oFodEtiQCc9pFcP9Xbr3wLESKq8Ui1VGPlNpLG2TUHw==} hasBin: true '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -4480,8 +4748,8 @@ packages: '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - '@types/d3-shape@3.1.7': - resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} @@ -4492,6 +4760,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -4525,8 +4796,8 @@ packages: '@types/linkify-it@3.0.5': resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} - '@types/lodash@4.17.20': - resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + '@types/lodash@4.17.23': + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} '@types/markdown-it@12.2.3': resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==} @@ -4547,8 +4818,8 @@ packages: '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} - '@types/node@25.2.1': - resolution: {integrity: sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==} + '@types/node@25.2.3': + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} '@types/nodemailer@7.0.9': resolution: {integrity: sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==} @@ -4570,8 +4841,8 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.13': - resolution: {integrity: sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} @@ -4591,63 +4862,63 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.54.0': - resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + '@typescript-eslint/eslint-plugin@8.55.0': + resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.54.0 + '@typescript-eslint/parser': ^8.55.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.54.0': - resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + '@typescript-eslint/parser@8.55.0': + resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.54.0': - resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + '@typescript-eslint/project-service@8.55.0': + resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.54.0': - resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + '@typescript-eslint/scope-manager@8.55.0': + resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.54.0': - resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + '@typescript-eslint/tsconfig-utils@8.55.0': + resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.54.0': - resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + '@typescript-eslint/type-utils@8.55.0': + resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.54.0': - resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + '@typescript-eslint/types@8.55.0': + resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.54.0': - resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + '@typescript-eslint/typescript-estree@8.55.0': + resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.54.0': - resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + '@typescript-eslint/utils@8.55.0': + resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.54.0': - resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + '@typescript-eslint/visitor-keys@8.55.0': + resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -4763,6 +5034,35 @@ packages: peerDependencies: '@urql/core': ^5.0.0 + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -4906,13 +5206,13 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -4960,6 +5260,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -5001,13 +5305,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.8.28: - resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} - hasBin: true - baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true @@ -5024,9 +5321,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -5044,11 +5338,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5057,16 +5346,23 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - builtins@5.1.0: resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -5089,18 +5385,16 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001754: - resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} - - caniuse-lite@1.0.30001757: - resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} - caniuse-lite@1.0.30001769: resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5138,6 +5432,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -5168,6 +5466,10 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -5219,6 +5521,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -5235,9 +5541,16 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + constant-case@2.0.0: resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==} @@ -5263,10 +5576,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} - engines: {node: '>=18'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -5282,9 +5591,6 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-env@10.1.0: resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} engines: {node: '>=20'} @@ -5294,8 +5600,8 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - css-declaration-sorter@7.3.0: - resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} + css-declaration-sorter@7.3.1: + resolution: {integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 @@ -5357,8 +5663,8 @@ packages: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-interpolate@3.0.1: @@ -5447,8 +5753,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-named-character-reference@1.2.0: - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -5502,10 +5808,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -5562,9 +5864,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.253: - resolution: {integrity: sha512-O0tpQ/35rrgdiGQ0/OFWhy1itmd9A6TY9uQzlqj3hKSu/aYpe7UIn5d7CU2N9myH6biZiWF3VMZVuup8pw5U9w==} - electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} @@ -5584,10 +5883,6 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} - engines: {node: '>=10.13.0'} - enhanced-resolve@5.19.0: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} @@ -5618,6 +5913,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5634,6 +5932,11 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5671,8 +5974,8 @@ packages: typescript: optional: true - eslint-config-turbo@2.8.3: - resolution: {integrity: sha512-cx9nT7IbP+insVejZPL4n55pkjPTlkWdjh3p/ZZL3W8BLt+2O1bWFJnT3lVyIGcC8v/4s6nZZPL+/id37zDdVA==} + eslint-config-turbo@2.8.5: + resolution: {integrity: sha512-H4A/Upj+s7lD/HQlwZyryjss149U5XYKmheXHFtorMyEFzuVbZD1JN4o7ZPoBFX7khgGT1vDHla6YIKLN6JHJA==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -5742,8 +6045,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-turbo@2.8.3: - resolution: {integrity: sha512-9ACQrrjzOfrbBGG1CqzyC67NtOSRcA+vyc9cjbyLyIoVtcK27czO7/WM+R5K3Opz0fb4Uezo6X+csMfL//RfJQ==} + eslint-plugin-turbo@2.8.5: + resolution: {integrity: sha512-6PaUEaIuh6absmV0v69dSvh//p1rcNiOtK1KXqYXqp8DUP57Jif6uQF0qxziV5XnyNu+A9Viytq8mAyj64kryg==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -5783,8 +6086,8 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -5808,6 +6111,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5839,6 +6145,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express-rate-limit@8.2.1: resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} @@ -5862,8 +6172,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-equals@5.3.3: - resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} engines: {node: '>=6.0.0'} fast-glob@3.2.12: @@ -5890,8 +6200,8 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -5933,6 +6243,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -6018,8 +6331,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.13.5: - resolution: {integrity: sha512-v4/4xAEpBRp6SvCkWhnGCaLkJf9IwWzrsygJPxD/+p2/xPE3C5m2fA9FD0Ry9tG+Rqqq3gBzHSl6y1/T9V/tMQ==} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} get-uri@6.0.5: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} @@ -6141,8 +6454,8 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.11.8: - resolution: {integrity: sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==} + hono@4.11.9: + resolution: {integrity: sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==} engines: {node: '>=16.9.0'} html-escaper@2.0.2: @@ -6184,8 +6497,8 @@ packages: i18next-resources-to-backend@1.2.1: resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} - i18next@25.8.4: - resolution: {integrity: sha512-a9A0MnUjKvzjEN/26ZY1okpra9kA8MEwzYEz1BNm+IyxUKPRH6ihf0p7vj8YvULwZHKHl3zkJ6KOt4hewxBecQ==} + i18next@25.8.5: + resolution: {integrity: sha512-TApjhgqQU6P7BQlpCTv6zQuXrYAP9rjYWgx2Nm8dsq+Zg9yJlAz+iR16/w7uVtTlSoULbqPTfqYjMK/DAQI+Ng==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -6210,9 +6523,6 @@ packages: idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -6259,10 +6569,6 @@ packages: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} - inquirer@8.2.7: - resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} - engines: {node: '>=12.0.0'} - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -6435,10 +6741,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - is-upper-case@1.1.2: resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==} @@ -6547,8 +6849,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - ky@1.14.0: - resolution: {integrity: sha512-Rczb6FMM6JT0lvrOlP5WUOCB7s9XKxzwgErzhKlKde1bEV90FXplV1o87fpt4PU/asJFiqjYJxAJyzJhcrxOsQ==} + ky@1.14.3: + resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==} engines: {node: '>=18'} language-subtag-registry@0.3.23: @@ -6565,8 +6867,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lib0@0.2.114: - resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} + lib0@0.2.117: + resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==} engines: {node: '>=16'} hasBin: true @@ -6647,6 +6949,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + loader-runner@4.3.1: resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} @@ -6655,8 +6961,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.22: - resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} lodash.deburr@4.1.0: resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} @@ -6674,9 +6980,6 @@ packages: lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} @@ -6684,10 +6987,6 @@ packages: resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} engines: {node: '>=8'} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -6723,9 +7022,6 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -6941,6 +7237,9 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -6954,6 +7253,13 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -7048,8 +7354,8 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nodemailer@8.0.0: - resolution: {integrity: sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==} + nodemailer@8.0.1: + resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -7103,6 +7409,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -7130,10 +7439,6 @@ packages: resolution: {integrity: sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==} engines: {node: '>=8'} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} @@ -7243,6 +7548,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -7284,21 +7592,28 @@ packages: pino-std-serializers@7.1.0: resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - pino@10.3.0: - resolution: {integrity: sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==} + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} hasBin: true + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - playwright-core@1.58.1: - resolution: {integrity: sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} engines: {node: '>=18'} hasBin: true - playwright@1.58.1: - resolution: {integrity: sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==} + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} engines: {node: '>=18'} hasBin: true @@ -7348,6 +7663,24 @@ packages: peerDependencies: postcss: ^8.4.32 + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss-merge-longhand@7.0.5: resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -7456,8 +7789,8 @@ packages: peerDependencies: postcss: ^8.4.32 - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-svgo@7.1.0: @@ -7600,14 +7933,14 @@ packages: prosemirror-state@1.4.4: resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} - prosemirror-tables@1.8.1: - resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==} + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} - prosemirror-transform@1.10.5: - resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==} + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} - prosemirror-view@1.41.3: - resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==} + prosemirror-view@1.41.6: + resolution: {integrity: sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -7668,8 +8001,8 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-day-picker@9.13.0: - resolution: {integrity: sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==} + react-day-picker@9.13.2: + resolution: {integrity: sha512-IMPiXfXVIAuR5Yk58DDPBC8QKClrhdXV+Tr/alBrwrHUw0qDDYB1m5zPNuTnnPIr/gmJ4ChMxmtqPdxm8+R4Eg==} engines: {node: '>=18'} peerDependencies: react: '>=16.8.0' @@ -7679,8 +8012,8 @@ packages: peerDependencies: react: ^19.2.4 - react-dropzone@14.4.0: - resolution: {integrity: sha512-8VvsHqg9WGAr+wAnP0oVErK5HOwAoTOzRsxLPzbBXrtXtFfukkxMyuvdI/lJ+5OxtsrzmvWE5Eoo3Y4hMsaxpA==} + react-dropzone@14.4.1: + resolution: {integrity: sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==} engines: {node: '>= 10.13'} peerDependencies: react: '>= 16.8 || 18.0.0' @@ -7723,8 +8056,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -7769,14 +8102,14 @@ packages: resolution: {integrity: sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==} engines: {node: ^20.17.0 || >=22.9.0} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -7802,8 +8135,8 @@ packages: registry-auth-token@3.3.2: resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} - registry-auth-token@5.1.0: - resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} engines: {node: '>=14'} registry-url@3.1.0: @@ -7829,6 +8162,10 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -7902,8 +8239,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.4.3: - resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -7936,11 +8274,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -8006,6 +8339,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -8051,8 +8387,8 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} @@ -8075,6 +8411,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -8082,6 +8422,9 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.11: resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} engines: {node: '>=6'} @@ -8090,6 +8433,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -8125,9 +8471,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -8186,8 +8529,13 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - supabase@2.75.5: - resolution: {integrity: sha512-gjCFvMTJ51HDPEGkvLroGgA4GEB5VhU136Gc/9BZgaHogqAKvTs7M28xlPcG5flGzmZjiqlNP1PcgzuQdw9fxQ==} + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supabase@2.76.7: + resolution: {integrity: sha512-tUseXvr7uLkw665cHdY3mg8NTHL5GABViRk7OhJkkbpOzvAJW/ydxQWAwLTcvXaI4P+cjbAmUgIv+6m8lOUe1Q==} engines: {npm: '>=8'} hasBin: true @@ -8219,8 +8567,8 @@ packages: swap-case@1.1.2: resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} - tabbable@6.3.0: - resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==} + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -8262,6 +8610,13 @@ packages: engines: {node: '>=10'} hasBin: true + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@4.0.0: resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} engines: {node: '>=20'} @@ -8278,9 +8633,15 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -8292,6 +8653,10 @@ packages: tinygradient@1.1.5: resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} @@ -8317,6 +8682,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -8326,19 +8695,8 @@ packages: ts-case-convert@2.1.0: resolution: {integrity: sha512-Ye79el/pHYXfoew6kqhMwCoxp4NWjKNcm2kBzpmEMIU9dd9aBmHNNFtZ+WTm0rz1ngyDmfqDXDlyUnBXayiD0w==} - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -8349,38 +8707,62 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-darwin-64@2.7.6: - resolution: {integrity: sha512-bYu0qnWju2Ha3EbIkPCk1SMLT3sltKh1P/Jy5FER6BmH++H5z+T5MHh3W1Xoers9rk4N1VdKvog9FO1pxQyjhw==} + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo-darwin-64@2.8.5: + resolution: {integrity: sha512-3wngnZrjRh6nXjNFURY9yKV7ysTF2gibpzkQjo4/c4eWjGt39q5p/A/wpxpnVBzyT1/auaPuzxJA98Sv1ZCOJg==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.7.6: - resolution: {integrity: sha512-KCxTf3Y1hgNLYIWRLw8bwH8Zie9RyCGoxAlXYsCBI/YNqBSR+ZZK9KYzFxAqDaVaNvTwLFv3rJRGsXOFWg4+Uw==} + turbo-darwin-arm64@2.8.5: + resolution: {integrity: sha512-QAFH6X9h8M4G8uTVRBQFw6EN0LmvYYyNEA2EKJOEogyeULSUA04M+FzYuSScl6uS9P78fzOCC0hhzk/Eqo4GQg==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.7.6: - resolution: {integrity: sha512-vjoU8zIfNgvJR3cMitgw7inEoi6bmuVuFawDl5yKtxjAEhDktFdRBpGS3WojD4l3BklBbIK689ssXcGf21LxRA==} + turbo-linux-64@2.8.5: + resolution: {integrity: sha512-o01d5g3CZWavW6vP0KD8wJwdqoHaLuOAb3PvuWY95JEcyPRAKGKNxsTW2xNifY4UYENwSVktFZXZlIO2qnrEmg==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.7.6: - resolution: {integrity: sha512-TcMpBvTqZf+1DptrVYLbZls7WY1UVNDTGaf0bo7/GCgWYv5eZHCVo4Td7kCJeDU4glbXg67REX0md0S0V6ghMg==} + turbo-linux-arm64@2.8.5: + resolution: {integrity: sha512-YSNVEUeFVGsA2pmPOokiximJOhKQnrg/M7JlJZzefjmp+j4Raj7YqLwK8pQRbAO4XDoojkf4tvmy+mRVW9O0gQ==} cpu: [arm64] os: [linux] - turbo-windows-64@2.7.6: - resolution: {integrity: sha512-1/MhkYldiihjneY8QnnDMbAkHXn/udTWSVYS94EMlkE9AShozsLTTOT1gDOpX06EfEW5njP09suhMvxbvwuwpQ==} + turbo-windows-64@2.8.5: + resolution: {integrity: sha512-X0MMT+IwWS+veX8h9/SO3+gkorcuGi0nu8CIg0kBhaqbC6Me0tChvHYQpkXj/+5qG5oFpjgkCSCip4/KYesZtg==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.7.6: - resolution: {integrity: sha512-0wDVnUJLFAWm4ZzOQFDkbyyUqaszorTGf3Rdc22IRIyJTTLd6ajqdb+cWD89UZ1RKr953+PZR1gqgWQY4PDuhA==} + turbo-windows-arm64@2.8.5: + resolution: {integrity: sha512-YBHZ1a0y8J0ITKv4TgujMFk04y84KimPDg8Br2lQaywrj3i2eRXLVuxhPi0Sqw+QuoqBVNKsHigxShA/w+rLLQ==} cpu: [arm64] os: [win32] - turbo@2.7.6: - resolution: {integrity: sha512-PO9AvJLEsNLO+EYhF4zB+v10hOjsJe5kJW+S6tTbRv+TW7gf1Qer4mfjP9h3/y9h8ZiPvOrenxnEgDtFgaM5zw==} + turbo@2.8.5: + resolution: {integrity: sha512-pUhV1czFZNGOwnwCLaO727uSDH4botIrhOb/AAFqzfi3x4eeRZoOcAjoidnXD/dwCAw0HJf3+Sy4c2e8SlQKxQ==} hasBin: true tw-animate-css@1.4.0: @@ -8418,8 +8800,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.54.0: - resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + typescript-eslint@8.55.0: + resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -8430,6 +8812,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -8454,8 +8839,8 @@ packages: unist-util-visit-parents@6.0.2: resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -8471,12 +8856,6 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -8536,9 +8915,6 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - validate-npm-package-name@5.0.0: resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -8557,6 +8933,80 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -8590,8 +9040,8 @@ packages: webpack-virtual-modules@0.5.0: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} - webpack@5.102.1: - resolution: {integrity: sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==} + webpack@5.105.1: + resolution: {integrity: sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -8624,6 +9074,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wonka@6.3.5: resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} @@ -8694,8 +9149,8 @@ packages: y-protocols: ^1.0.1 yjs: ^13.5.38 - y-protocols@1.0.6: - resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} + y-protocols@1.0.7: + resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: yjs: ^13.0.0 @@ -8716,14 +9171,10 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yjs@13.6.27: - resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} + yjs@13.6.29: + resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -8732,6 +9183,10 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -8767,12 +9222,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -8801,14 +9250,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.0 @@ -8827,13 +9268,6 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 @@ -8861,10 +9295,6 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 - '@babel/parser@7.28.5': - dependencies: - '@babel/types': 7.28.5 - '@babel/parser@7.29.0': dependencies: '@babel/types': 7.29.0 @@ -8873,34 +9303,14 @@ snapshots: dependencies: core-js-pure: 3.48.0 - '@babel/runtime@7.28.4': {} - '@babel/runtime@7.28.6': {} - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 '@babel/parser': 7.29.0 '@babel/types': 7.29.0 - '@babel/traverse@7.28.5': - 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.4.3 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -8913,11 +9323,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -8927,17 +9332,13 @@ snapshots: '@corex/deepmerge@4.0.43': {} - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@date-fns/tz@1.4.1': {} '@discoveryjs/json-ext@0.5.7': {} - '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@emnapi/core@1.8.1': dependencies: @@ -8957,8 +9358,8 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/runtime': 7.28.4 + '@babel/helper-module-imports': 7.28.6 + '@babel/runtime': 7.28.6 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -9013,6 +9414,84 @@ snapshots: '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -9059,30 +9538,30 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@faker-js/faker@10.2.0': {} + '@faker-js/faker@10.3.0': {} - '@floating-ui/core@1.7.3': + '@floating-ui/core@1.7.4': dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.4': + '@floating-ui/dom@1.7.5': dependencies: - '@floating-ui/core': 1.7.3 + '@floating-ui/core': 1.7.4 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@floating-ui/dom': 1.7.4 + '@floating-ui/dom': 1.7.5 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) '@floating-ui/react@0.24.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - tabbable: 6.3.0 + tabbable: 6.4.0 '@floating-ui/utils@0.2.10': {} @@ -9116,9 +9595,9 @@ snapshots: dependencies: graphql: 16.12.0 - '@hono/node-server@1.19.9(hono@4.11.8)': + '@hono/node-server@1.19.9(hono@4.11.9)': dependencies: - hono: 4.11.8 + hono: 4.11.9 '@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.4))': dependencies: @@ -9233,29 +9712,147 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/external-editor@1.0.3(@types/node@25.2.1)': + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.2.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.2.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/confirm@5.1.21(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/core@10.3.2(@types/node@25.2.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.2.3) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/editor@4.2.23(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/expand@4.0.23(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/external-editor@1.0.3(@types/node@25.2.3)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 - '@internationalized/date@3.10.0': + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@25.2.3)': dependencies: - '@swc/helpers': 0.5.17 + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/number@3.0.23(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/password@4.0.23(@types/node@25.2.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/prompts@7.10.1(@types/node@25.2.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.2.3) + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@inquirer/editor': 4.2.23(@types/node@25.2.3) + '@inquirer/expand': 4.0.23(@types/node@25.2.3) + '@inquirer/input': 4.3.1(@types/node@25.2.3) + '@inquirer/number': 3.0.23(@types/node@25.2.3) + '@inquirer/password': 4.0.23(@types/node@25.2.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.2.3) + '@inquirer/search': 3.2.2(@types/node@25.2.3) + '@inquirer/select': 4.4.2(@types/node@25.2.3) + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/rawlist@4.1.11(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/search@3.2.2(@types/node@25.2.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.2.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/select@4.4.2(@types/node@25.2.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.2.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.2.3 + + '@inquirer/type@3.0.10(@types/node@25.2.3)': + optionalDependencies: + '@types/node': 25.2.3 + + '@internationalized/date@3.11.0': + dependencies: + '@swc/helpers': 0.5.18 '@internationalized/message@3.1.8': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 intl-messageformat: 10.7.18 '@internationalized/number@3.6.5': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 '@internationalized/string@3.2.7': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 '@isaacs/cliui@8.0.2': dependencies: @@ -9294,141 +9891,136 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@juggle/resize-observer@3.4.0': {} - '@keystar/ui@0.7.19(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@keystar/ui@0.7.19(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@emotion/css': 11.13.5 '@floating-ui/react': 0.24.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@internationalized/date': 3.10.0 + '@internationalized/date': 3.11.0 '@internationalized/string': 3.2.7 - '@react-aria/actiongroup': 3.7.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/breadcrumbs': 3.5.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/button': 3.14.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/calendar': 3.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/checkbox': 3.16.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/combobox': 3.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/datepicker': 3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/dialog': 3.5.31(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/dnd': 3.11.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/gridlist': 3.14.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/link': 3.8.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/listbox': 3.15.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/actiongroup': 3.7.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/breadcrumbs': 3.5.31(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/button': 3.14.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/calendar': 3.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/checkbox': 3.16.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/combobox': 3.14.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/datepicker': 3.16.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/dialog': 3.5.33(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/dnd': 3.11.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/gridlist': 3.14.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/link': 3.8.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/listbox': 3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/menu': 3.19.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/meter': 3.4.27(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/numberfield': 3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/progress': 3.4.27(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/radio': 3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/searchfield': 3.8.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/select': 3.17.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/separator': 3.4.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/menu': 3.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/meter': 3.4.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/numberfield': 3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/progress': 3.4.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/radio': 3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/searchfield': 3.8.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/select': 3.17.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/separator': 3.4.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/ssr': 3.9.10(react@19.2.4) - '@react-aria/switch': 3.7.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/table': 3.17.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/tabs': 3.10.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/tag': 3.7.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/textfield': 3.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/switch': 3.7.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/table': 3.17.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/tabs': 3.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/tag': 3.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/textfield': 3.18.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/toast': 3.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/tooltip': 3.8.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/virtualizer': 4.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/calendar': 3.9.0(react@19.2.4) - '@react-stately/checkbox': 3.7.2(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/combobox': 3.12.0(react@19.2.4) - '@react-stately/data': 3.14.1(react@19.2.4) - '@react-stately/datepicker': 3.15.2(react@19.2.4) - '@react-stately/dnd': 3.7.1(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/layout': 4.5.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-stately/menu': 3.9.8(react@19.2.4) - '@react-stately/numberfield': 3.10.2(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-stately/radio': 3.11.2(react@19.2.4) - '@react-stately/searchfield': 3.5.16(react@19.2.4) - '@react-stately/select': 3.8.0(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-stately/table': 3.15.1(react@19.2.4) - '@react-stately/tabs': 3.8.6(react@19.2.4) + '@react-aria/tooltip': 3.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/virtualizer': 4.1.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/visually-hidden': 3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/calendar': 3.9.2(react@19.2.4) + '@react-stately/checkbox': 3.7.4(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/combobox': 3.12.2(react@19.2.4) + '@react-stately/data': 3.15.1(react@19.2.4) + '@react-stately/datepicker': 3.16.0(react@19.2.4) + '@react-stately/dnd': 3.7.3(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/layout': 4.5.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-stately/menu': 3.9.10(react@19.2.4) + '@react-stately/numberfield': 3.10.4(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-stately/radio': 3.11.4(react@19.2.4) + '@react-stately/searchfield': 3.5.18(react@19.2.4) + '@react-stately/select': 3.9.1(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-stately/table': 3.15.3(react@19.2.4) + '@react-stately/tabs': 3.8.8(react@19.2.4) '@react-stately/toast': 3.1.0(react@19.2.4) - '@react-stately/toggle': 3.9.2(react@19.2.4) - '@react-stately/tooltip': 3.5.8(react@19.2.4) - '@react-stately/tree': 3.9.3(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/actionbar': 3.1.19(react@19.2.4) - '@react-types/actiongroup': 3.4.21(react@19.2.4) - '@react-types/breadcrumbs': 3.7.17(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/calendar': 3.8.0(react@19.2.4) - '@react-types/combobox': 3.13.9(react@19.2.4) - '@react-types/datepicker': 3.13.2(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/menu': 3.10.5(react@19.2.4) - '@react-types/numberfield': 3.8.15(react@19.2.4) - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/radio': 3.9.2(react@19.2.4) - '@react-types/select': 3.11.0(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/switch': 3.5.15(react@19.2.4) - '@react-types/table': 3.13.4(react@19.2.4) - '@react-types/tabs': 3.3.19(react@19.2.4) - '@types/react': 19.2.13 + '@react-stately/toggle': 3.9.4(react@19.2.4) + '@react-stately/tooltip': 3.5.10(react@19.2.4) + '@react-stately/tree': 3.9.5(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-stately/virtualizer': 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/actionbar': 3.1.20(react@19.2.4) + '@react-types/actiongroup': 3.4.22(react@19.2.4) + '@react-types/breadcrumbs': 3.7.18(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/calendar': 3.8.2(react@19.2.4) + '@react-types/combobox': 3.13.11(react@19.2.4) + '@react-types/datepicker': 3.13.4(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/menu': 3.10.6(react@19.2.4) + '@react-types/numberfield': 3.8.17(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/radio': 3.9.3(react@19.2.4) + '@react-types/select': 3.12.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/switch': 3.5.16(react@19.2.4) + '@react-types/table': 3.13.5(react@19.2.4) + '@react-types/tabs': 3.3.21(react@19.2.4) + '@types/react': 19.2.14 emery: 1.4.4 facepaint: 1.2.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) transitivePeerDependencies: - supports-color - '@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@braintree/sanitize-url': 6.0.4 '@emotion/weak-memoize': 0.3.1 '@floating-ui/react': 0.24.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@internationalized/string': 3.2.7 - '@keystar/ui': 0.7.19(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@markdoc/markdoc': 0.4.0(@types/react@19.2.13)(react@19.2.4) - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@keystar/ui': 0.7.19(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@markdoc/markdoc': 0.4.0(@types/react@19.2.14)(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/visually-hidden': 3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) '@sindresorhus/slugify': 1.1.2 - '@toeverything/y-indexeddb': 0.10.0-canary.9(yjs@13.6.27) + '@toeverything/y-indexeddb': 0.10.0-canary.9(yjs@13.6.29) '@ts-gql/tag': 0.7.3(graphql@16.12.0) - '@types/react': 19.2.13 + '@types/react': 19.2.14 '@urql/core': 5.2.0(graphql@16.12.0) '@urql/exchange-auth': 2.2.1(@urql/core@5.2.0(graphql@16.12.0)) '@urql/exchange-graphcache': 7.2.4(@urql/core@5.2.0(graphql@16.12.0))(graphql@16.12.0) '@urql/exchange-persisted': 4.3.1(@urql/core@5.2.0(graphql@16.12.0)) - cookie: 1.0.2 + cookie: 1.1.1 emery: 1.4.4 escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 @@ -9437,7 +10029,7 @@ snapshots: ignore: 5.3.2 is-hotkey: 0.2.0 js-yaml: 4.1.1 - lib0: 0.2.114 + lib0: 0.2.117 lru-cache: 10.4.3 match-sorter: 6.3.4 mdast-util-from-markdown: 2.0.2 @@ -9457,9 +10049,9 @@ snapshots: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.4 prosemirror-state: 1.4.4 - prosemirror-tables: 1.8.1 - prosemirror-transform: 1.10.5 - prosemirror-view: 1.41.3 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) scroll-into-view-if-needed: 3.1.0 @@ -9467,22 +10059,22 @@ snapshots: slate-history: 0.86.0(slate@0.91.4) slate-react: 0.91.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(slate@0.91.4) superstruct: 1.0.4 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 urql: 4.2.2(@urql/core@5.2.0(graphql@16.12.0))(react@19.2.4) - y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29) + y-protocols: 1.0.7(yjs@13.6.29) + yjs: 13.6.29 transitivePeerDependencies: - next - supports-color - '@keystatic/next@5.0.4(@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@keystatic/next@5.0.4(@keystatic/core@0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/runtime': 7.28.4 - '@keystatic/core': 0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@types/react': 19.2.13 + '@babel/runtime': 7.28.6 + '@keystatic/core': 0.5.48(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/react': 19.2.14 chokidar: 3.6.0 - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) server-only: 0.0.1 @@ -9495,12 +10087,12 @@ snapshots: '@supabase/supabase-js': 2.95.3 ts-case-convert: 2.1.0 - '@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.20(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + '@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3)(@tanstack/react-query@5.90.21(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': dependencies: '@makerkit/data-loader-supabase-core': 0.0.10(@supabase/postgrest-js@2.95.3)(@supabase/supabase-js@2.95.3) '@supabase/supabase-js': 2.95.3 - '@tanstack/react-query': 5.90.20(react@19.2.4) - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-query': 5.90.21(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 transitivePeerDependencies: - '@supabase/postgrest-js' @@ -9515,7 +10107,7 @@ snapshots: parse-github-url: 1.0.3 picocolors: 1.1.1 sembear: 0.7.0 - semver: 7.7.3 + semver: 7.7.4 tinyexec: 1.0.2 validate-npm-package-name: 6.0.2 @@ -9534,17 +10126,17 @@ snapshots: js-yaml: 4.1.1 tinyglobby: 0.2.15 - '@markdoc/markdoc@0.4.0(@types/react@19.2.13)(react@19.2.4)': + '@markdoc/markdoc@0.4.0(@types/react@19.2.14)(react@19.2.4)': optionalDependencies: '@types/markdown-it': 12.2.3 - '@types/react': 19.2.13 + '@types/react': 19.2.14 react: 19.2.4 - '@markdoc/markdoc@0.5.4(@types/react@19.2.13)(react@19.2.4)': + '@markdoc/markdoc@0.5.4(@types/react@19.2.14)(react@19.2.4)': optionalDependencies: '@types/linkify-it': 3.0.5 '@types/markdown-it': 12.2.3 - '@types/react': 19.2.13 + '@types/react': 19.2.14 react: 19.2.4 '@marsidev/react-turnstile@1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': @@ -9554,7 +10146,7 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.8) + '@hono/node-server': 1.19.9(hono@4.11.9) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 @@ -9564,7 +10156,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.2.1(express@5.2.1) - hono: 4.11.8 + hono: 4.11.9 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -9630,13 +10222,13 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + fastq: 1.20.1 '@nolyfill/is-core-module@1.0.39': {} - '@nosecone/next@1.1.0(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': + '@nosecone/next@1.1.0(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))': dependencies: - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nosecone: 1.1.0 '@opentelemetry/api-logs@0.207.0': @@ -9894,9 +10486,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.58.1': + '@playwright/test@1.58.2': dependencies: - playwright: 1.58.1 + playwright: 1.58.2 '@pnpm/config.env-replace@1.1.0': {} @@ -9904,7 +10496,7 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.3.1': + '@pnpm/npm-conf@3.0.2': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 @@ -9923,1327 +10515,1327 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-context@1.1.2(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.1(@types/react@19.2.13)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) '@radix-ui/react-icons@1.3.2(react@19.2.4)': dependencies: react: 19.2.4 - '@radix-ui/react-id@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.1(@types/react@19.2.13)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.1(@types/react@19.2.13)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) '@radix-ui/rect': 1.1.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.1(@types/react@19.2.13)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.13)(react@19.2.4)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) '@radix-ui/rect@1.1.1': {} - '@react-aria/actiongroup@3.7.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/actiongroup@3.7.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-types/actiongroup': 3.4.21(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-types/actiongroup': 3.4.22(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/breadcrumbs@3.5.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/breadcrumbs@3.5.31(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/link': 3.8.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/breadcrumbs': 3.7.17(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/link': 3.8.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/breadcrumbs': 3.7.18(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/button@3.14.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/button@3.14.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/toggle': 3.9.2(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/toolbar': 3.0.0-beta.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/toggle': 3.9.4(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/calendar@3.9.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/calendar@3.9.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@internationalized/date': 3.11.0 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/calendar': 3.9.0(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/calendar': 3.8.0(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/calendar': 3.9.2(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/calendar': 3.8.2(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/checkbox@3.16.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/checkbox@3.16.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/toggle': 3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/checkbox': 3.7.2(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/toggle': 3.9.2(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/form': 3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/toggle': 3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/checkbox': 3.7.4(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/toggle': 3.9.4(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/combobox@3.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/combobox@3.14.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/listbox': 3.15.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/listbox': 3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/menu': 3.19.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/textfield': 3.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/combobox': 3.12.0(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/combobox': 3.13.9(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/menu': 3.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/textfield': 3.18.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/combobox': 3.12.2(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/combobox': 3.13.11(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/datepicker@3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/datepicker@3.16.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 + '@internationalized/date': 3.11.0 '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/form': 3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/datepicker': 3.15.2(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/calendar': 3.8.0(react@19.2.4) - '@react-types/datepicker': 3.13.2(react@19.2.4) - '@react-types/dialog': 3.5.22(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/form': 3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/spinbutton': 3.7.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/datepicker': 3.16.0(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/calendar': 3.8.2(react@19.2.4) + '@react-types/datepicker': 3.13.4(react@19.2.4) + '@react-types/dialog': 3.5.23(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/dialog@3.5.31(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/dialog@3.5.33(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/dialog': 3.5.22(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/dialog': 3.5.23(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/dnd@3.11.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/dnd@3.11.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@internationalized/string': 3.2.7 - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/dnd': 3.7.1(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/dnd': 3.7.3(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/focus@3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/focus@3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 clsx: 2.1.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/form@3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/form@3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/grid@3.14.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/grid@3.14.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/grid': 3.11.6(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/grid': 3.11.8(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/gridlist@3.14.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/gridlist@3.14.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/grid': 3.14.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-stately/tree': 3.9.3(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/grid': 3.14.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-stately/tree': 3.9.5(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/i18n@3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/i18n@3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 + '@internationalized/date': 3.11.0 '@internationalized/message': 3.1.8 '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 '@react-aria/ssr': 3.9.10(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/interactions@3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/interactions@3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@react-aria/ssr': 3.9.10(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-stately/flags': 3.1.2 - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/label@3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/label@3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/landmark@3.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/landmark@3.0.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@react-aria/link@3.8.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/link@3.8.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/link': 3.6.5(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/link': 3.6.6(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/listbox@3.15.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/listbox@3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-types/listbox': 3.7.4(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-types/listbox': 3.7.5(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) '@react-aria/live-announcer@3.4.4': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 - '@react-aria/menu@3.19.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/menu@3.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/overlays': 3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/menu': 3.9.8(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-stately/tree': 3.9.3(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/menu': 3.10.5(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/overlays': 3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/menu': 3.9.10(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-stately/tree': 3.9.5(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/menu': 3.10.6(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/meter@3.4.27(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/meter@3.4.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/progress': 3.4.27(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/meter': 3.4.13(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/progress': 3.4.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/meter': 3.4.14(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/numberfield@3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/numberfield@3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/textfield': 3.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/numberfield': 3.10.2(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/numberfield': 3.8.15(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/spinbutton': 3.7.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/textfield': 3.18.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/numberfield': 3.10.4(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/numberfield': 3.8.17(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/overlays@3.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/overlays@3.31.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/ssr': 3.9.10(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/visually-hidden': 3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/progress@3.4.27(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/progress@3.4.29(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/progress': 3.5.16(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/progress': 3.5.17(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/radio@3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/radio@3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/form': 3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/radio': 3.11.2(react@19.2.4) - '@react-types/radio': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/form': 3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/radio': 3.11.4(react@19.2.4) + '@react-types/radio': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/searchfield@3.8.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/searchfield@3.8.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/textfield': 3.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/searchfield': 3.5.16(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/searchfield': 3.6.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/textfield': 3.18.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/searchfield': 3.5.18(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/searchfield': 3.6.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/select@3.17.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/select@3.17.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/listbox': 3.15.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/menu': 3.19.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/select': 3.8.0(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/select': 3.11.0(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/form': 3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/listbox': 3.15.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/menu': 3.20.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/visually-hidden': 3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/select': 3.9.1(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/select': 3.12.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/selection@3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/selection@3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/separator@3.4.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/separator@3.4.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/spinbutton@3.6.19(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/spinbutton@3.7.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) '@react-aria/ssr@3.9.10(react@19.2.4)': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-aria/switch@3.7.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/switch@3.7.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/toggle': 3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/toggle': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/switch': 3.5.15(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/toggle': 3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/toggle': 3.9.4(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/switch': 3.5.16(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/table@3.17.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/table@3.17.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/grid': 3.14.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/grid': 3.14.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/collections': 3.12.8(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/visually-hidden': 3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) '@react-stately/flags': 3.1.2 - '@react-stately/table': 3.15.1(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/table': 3.13.4(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/table': 3.15.3(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/table': 3.13.5(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/tabs@3.10.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/tabs@3.11.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/tabs': 3.8.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/tabs': 3.3.19(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/tabs': 3.8.8(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/tabs': 3.3.21(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/tag@3.7.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/tag@3.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/gridlist': 3.14.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/selection': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/gridlist': 3.14.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/selection': 3.27.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/textfield@3.18.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/textfield@3.18.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/label': 3.7.22(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/textfield': 3.12.6(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/form': 3.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/label': 3.7.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/textfield': 3.12.7(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) '@react-aria/toast@3.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/landmark': 3.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/landmark': 3.0.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@react-stately/toast': 3.1.0(react@19.2.4) - '@react-types/button': 3.14.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/button': 3.15.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/toggle@3.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/toggle@3.12.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/toggle': 3.9.2(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/toggle': 3.9.4(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/toolbar@3.0.0-beta.21(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/toolbar@3.0.0-beta.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/tooltip@3.8.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/tooltip@3.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/tooltip': 3.5.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/tooltip': 3.4.21(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/tooltip': 3.5.10(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/tooltip': 3.5.1(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/utils@3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/utils@3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@react-aria/ssr': 3.9.10(react@19.2.4) '@react-stately/flags': 3.1.2 - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 clsx: 2.1.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/virtualizer@4.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/virtualizer@4.1.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/i18n': 3.12.15(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-stately/virtualizer': 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-aria/visually-hidden@3.8.28(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-aria/visually-hidden@3.8.30(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-aria/utils': 3.31.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-aria/utils': 3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -11371,366 +11963,367 @@ snapshots: dependencies: react: 19.2.4 - '@react-stately/calendar@3.9.0(react@19.2.4)': + '@react-stately/calendar@3.9.2(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/calendar': 3.8.0(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@internationalized/date': 3.11.0 + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/calendar': 3.8.2(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/checkbox@3.7.2(react@19.2.4)': + '@react-stately/checkbox@3.7.4(react@19.2.4)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/collections@3.12.8(react@19.2.4)': + '@react-stately/collections@3.12.9(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/combobox@3.12.0(react@19.2.4)': + '@react-stately/combobox@3.12.2(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/combobox': 3.13.9(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/combobox': 3.13.11(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/data@3.14.1(react@19.2.4)': + '@react-stately/data@3.15.1(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/datepicker@3.15.2(react@19.2.4)': + '@react-stately/datepicker@3.16.0(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 + '@internationalized/date': 3.11.0 + '@internationalized/number': 3.6.5 '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/datepicker': 3.13.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/datepicker': 3.13.4(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/dnd@3.7.1(react@19.2.4)': + '@react-stately/dnd@3.7.3(react@19.2.4)': dependencies: - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 '@react-stately/flags@3.1.2': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 - '@react-stately/form@3.2.2(react@19.2.4)': + '@react-stately/form@3.2.3(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/grid@3.11.6(react@19.2.4)': + '@react-stately/grid@3.11.8(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/layout@4.5.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-stately/layout@4.5.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/table': 3.15.1(react@19.2.4) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/table': 3.13.4(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/table': 3.15.3(react@19.2.4) + '@react-stately/virtualizer': 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/table': 3.13.5(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-stately/list@3.13.1(react@19.2.4)': + '@react-stately/list@3.13.3(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/menu@3.9.8(react@19.2.4)': + '@react-stately/menu@3.9.10(react@19.2.4)': dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-types/menu': 3.10.5(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-types/menu': 3.10.6(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/numberfield@3.10.2(react@19.2.4)': + '@react-stately/numberfield@3.10.4(react@19.2.4)': dependencies: '@internationalized/number': 3.6.5 - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/numberfield': 3.8.15(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/numberfield': 3.8.17(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/overlays@3.6.20(react@19.2.4)': + '@react-stately/overlays@3.6.22(react@19.2.4)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/overlays': 3.9.2(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/radio@3.11.2(react@19.2.4)': + '@react-stately/radio@3.11.4(react@19.2.4)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/radio': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/radio': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/searchfield@3.5.16(react@19.2.4)': + '@react-stately/searchfield@3.5.18(react@19.2.4)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/searchfield': 3.6.6(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/searchfield': 3.6.7(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/select@3.8.0(react@19.2.4)': + '@react-stately/select@3.9.1(react@19.2.4)': dependencies: - '@react-stately/form': 3.2.2(react@19.2.4) - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/select': 3.11.0(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/form': 3.2.3(react@19.2.4) + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/select': 3.12.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/selection@3.20.6(react@19.2.4)': + '@react-stately/selection@3.20.8(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/table@3.15.1(react@19.2.4)': + '@react-stately/table@3.15.3(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) + '@react-stately/collections': 3.12.9(react@19.2.4) '@react-stately/flags': 3.1.2 - '@react-stately/grid': 3.11.6(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/table': 3.13.4(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/grid': 3.11.8(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/table': 3.13.5(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/tabs@3.8.6(react@19.2.4)': + '@react-stately/tabs@3.8.8(react@19.2.4)': dependencies: - '@react-stately/list': 3.13.1(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/tabs': 3.3.19(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/list': 3.13.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/tabs': 3.3.21(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 '@react-stately/toast@3.1.0(react@19.2.4)': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) - '@react-stately/toggle@3.9.2(react@19.2.4)': + '@react-stately/toggle@3.9.4(react@19.2.4)': dependencies: - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/checkbox': 3.10.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/checkbox': 3.10.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/tooltip@3.5.8(react@19.2.4)': + '@react-stately/tooltip@3.5.10(react@19.2.4)': dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.4) - '@react-types/tooltip': 3.4.21(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/overlays': 3.6.22(react@19.2.4) + '@react-types/tooltip': 3.5.1(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/tree@3.9.3(react@19.2.4)': + '@react-stately/tree@3.9.5(react@19.2.4)': dependencies: - '@react-stately/collections': 3.12.8(react@19.2.4) - '@react-stately/selection': 3.20.6(react@19.2.4) - '@react-stately/utils': 3.10.8(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-stately/collections': 3.12.9(react@19.2.4) + '@react-stately/selection': 3.20.8(react@19.2.4) + '@react-stately/utils': 3.11.0(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/utils@3.10.8(react@19.2.4)': + '@react-stately/utils@3.11.0(react@19.2.4)': dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 react: 19.2.4 - '@react-stately/virtualizer@4.4.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-stately/virtualizer@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) - '@swc/helpers': 0.5.17 + '@react-types/shared': 3.33.0(react@19.2.4) + '@swc/helpers': 0.5.18 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@react-types/actionbar@3.1.19(react@19.2.4)': + '@react-types/actionbar@3.1.20(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/actiongroup@3.4.21(react@19.2.4)': + '@react-types/actiongroup@3.4.22(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/breadcrumbs@3.7.17(react@19.2.4)': + '@react-types/breadcrumbs@3.7.18(react@19.2.4)': dependencies: - '@react-types/link': 3.6.5(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/link': 3.6.6(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/button@3.14.1(react@19.2.4)': + '@react-types/button@3.15.0(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/calendar@3.8.0(react@19.2.4)': + '@react-types/calendar@3.8.2(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 - '@react-types/shared': 3.32.1(react@19.2.4) + '@internationalized/date': 3.11.0 + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/checkbox@3.10.2(react@19.2.4)': + '@react-types/checkbox@3.10.3(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/combobox@3.13.9(react@19.2.4)': + '@react-types/combobox@3.13.11(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/datepicker@3.13.2(react@19.2.4)': + '@react-types/datepicker@3.13.4(react@19.2.4)': dependencies: - '@internationalized/date': 3.10.0 - '@react-types/calendar': 3.8.0(react@19.2.4) - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@internationalized/date': 3.11.0 + '@react-types/calendar': 3.8.2(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/dialog@3.5.22(react@19.2.4)': + '@react-types/dialog@3.5.23(react@19.2.4)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/grid@3.3.6(react@19.2.4)': + '@react-types/grid@3.3.7(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/link@3.6.5(react@19.2.4)': + '@react-types/link@3.6.6(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/listbox@3.7.4(react@19.2.4)': + '@react-types/listbox@3.7.5(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/menu@3.10.5(react@19.2.4)': + '@react-types/menu@3.10.6(react@19.2.4)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/meter@3.4.13(react@19.2.4)': + '@react-types/meter@3.4.14(react@19.2.4)': dependencies: - '@react-types/progress': 3.5.16(react@19.2.4) + '@react-types/progress': 3.5.17(react@19.2.4) react: 19.2.4 - '@react-types/numberfield@3.8.15(react@19.2.4)': + '@react-types/numberfield@3.8.17(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/overlays@3.9.2(react@19.2.4)': + '@react-types/overlays@3.9.3(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/progress@3.5.16(react@19.2.4)': + '@react-types/progress@3.5.17(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/radio@3.9.2(react@19.2.4)': + '@react-types/radio@3.9.3(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/searchfield@3.6.6(react@19.2.4)': + '@react-types/searchfield@3.6.7(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) - '@react-types/textfield': 3.12.6(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) + '@react-types/textfield': 3.12.7(react@19.2.4) react: 19.2.4 - '@react-types/select@3.11.0(react@19.2.4)': + '@react-types/select@3.12.1(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/shared@3.32.1(react@19.2.4)': + '@react-types/shared@3.33.0(react@19.2.4)': dependencies: react: 19.2.4 - '@react-types/switch@3.5.15(react@19.2.4)': + '@react-types/switch@3.5.16(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/table@3.13.4(react@19.2.4)': + '@react-types/table@3.13.5(react@19.2.4)': dependencies: - '@react-types/grid': 3.3.6(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/grid': 3.3.7(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/tabs@3.3.19(react@19.2.4)': + '@react-types/tabs@3.3.21(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/textfield@3.12.6(react@19.2.4)': + '@react-types/textfield@3.12.7(react@19.2.4)': dependencies: - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 - '@react-types/tooltip@3.4.21(react@19.2.4)': + '@react-types/tooltip@3.5.1(react@19.2.4)': dependencies: - '@react-types/overlays': 3.9.2(react@19.2.4) - '@react-types/shared': 3.32.1(react@19.2.4) + '@react-types/overlays': 3.9.3(react@19.2.4) + '@react-types/shared': 3.33.0(react@19.2.4) react: 19.2.4 '@rollup/plugin-commonjs@28.0.1(rollup@4.57.1)': @@ -11853,7 +12446,7 @@ snapshots: '@sentry-internal/browser-utils': 10.38.0 '@sentry/core': 10.38.0 - '@sentry/babel-plugin-component-annotate@4.9.0': {} + '@sentry/babel-plugin-component-annotate@4.9.1': {} '@sentry/browser@10.38.0': dependencies: @@ -11863,10 +12456,10 @@ snapshots: '@sentry-internal/replay-canvas': 10.38.0 '@sentry/core': 10.38.0 - '@sentry/bundler-plugin-core@4.9.0': + '@sentry/bundler-plugin-core@4.9.1': dependencies: '@babel/core': 7.29.0 - '@sentry/babel-plugin-component-annotate': 4.9.0 + '@sentry/babel-plugin-component-annotate': 4.9.1 '@sentry/cli': 2.58.4 dotenv: 16.6.1 find-up: 5.0.0 @@ -11923,20 +12516,20 @@ snapshots: '@sentry/core@10.38.0': {} - '@sentry/nextjs@10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.102.1)': + '@sentry/nextjs@10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.105.1)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.39.0 '@rollup/plugin-commonjs': 28.0.1(rollup@4.57.1) '@sentry-internal/browser-utils': 10.38.0 - '@sentry/bundler-plugin-core': 4.9.0 + '@sentry/bundler-plugin-core': 4.9.1 '@sentry/core': 10.38.0 '@sentry/node': 10.38.0 '@sentry/opentelemetry': 10.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) '@sentry/react': 10.38.0(react@19.2.4) '@sentry/vercel-edge': 10.38.0 - '@sentry/webpack-plugin': 4.9.0(webpack@5.102.1) - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@sentry/webpack-plugin': 4.9.1(webpack@5.105.1) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) rollup: 4.57.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: @@ -12025,12 +12618,12 @@ snapshots: '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) '@sentry/core': 10.38.0 - '@sentry/webpack-plugin@4.9.0(webpack@5.102.1)': + '@sentry/webpack-plugin@4.9.1(webpack@5.105.1)': dependencies: - '@sentry/bundler-plugin-core': 4.9.0 + '@sentry/bundler-plugin-core': 4.9.1 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.102.1 + webpack: 5.105.1 transitivePeerDependencies: - encoding - supports-color @@ -12045,6 +12638,8 @@ snapshots: escape-string-regexp: 2.0.0 lodash.deburr: 4.1.0 + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} '@stripe/react-stripe-js@5.6.0(@stripe/stripe-js@8.7.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': @@ -12103,14 +12698,14 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/helpers@0.5.17': + '@swc/helpers@0.5.18': dependencies: tslib: 2.8.1 '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.19.0 jiti: 2.6.1 lightningcss: 1.30.2 magic-string: 0.30.21 @@ -12178,7 +12773,7 @@ snapshots: '@tanstack/query-core@5.90.20': {} - '@tanstack/react-query@5.90.20(react@19.2.4)': + '@tanstack/react-query@5.90.21(react@19.2.4)': dependencies: '@tanstack/query-core': 5.90.20 react: 19.2.4 @@ -12191,23 +12786,23 @@ snapshots: '@tanstack/table-core@8.21.3': {} - '@toeverything/y-indexeddb@0.10.0-canary.9(yjs@13.6.27)': + '@toeverything/y-indexeddb@0.10.0-canary.9(yjs@13.6.29)': dependencies: idb: 7.1.1 nanoid: 5.1.6 - y-provider: 0.10.0-canary.9(yjs@13.6.27) - yjs: 13.6.27 + y-provider: 0.10.0-canary.9(yjs@13.6.29) + yjs: 13.6.29 '@tootallnate/quickjs-emscripten@0.23.0': {} '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.1)': dependencies: - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 - lodash-es: 4.17.22 + lodash-es: 4.17.23 minimatch: 9.0.5 parse-imports-exports: 0.2.4 prettier: 3.8.1 @@ -12220,42 +12815,31 @@ snapshots: graphql: 16.12.0 graphql-tag: 2.12.6(graphql@16.12.0) - '@tsconfig/node10@1.0.12': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - - '@turbo/gen@2.8.3(@types/node@25.2.1)(typescript@5.9.3)': + '@turbo/gen@2.8.5(@types/node@25.2.3)': dependencies: - '@turbo/workspaces': 2.8.3(@types/node@25.2.1) + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) + '@turbo/workspaces': 2.8.5(@types/node@25.2.3) commander: 10.0.0 fs-extra: 10.1.0 - inquirer: 8.2.7(@types/node@25.2.1) minimatch: 9.0.0 node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.5.0 - ts-node: 10.9.2(@types/node@25.2.1)(typescript@5.9.3) + tsx: 4.21.0 update-check: 1.5.4 validate-npm-package-name: 5.0.0 transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - '@types/node' - supports-color - - typescript - '@turbo/workspaces@2.8.3(@types/node@25.2.1)': + '@turbo/workspaces@2.8.5(@types/node@25.2.3)': dependencies: + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) commander: 10.0.0 execa: 5.1.1 fast-glob: 3.2.12 fs-extra: 10.1.0 gradient-string: 2.0.1 - inquirer: 8.2.7(@types/node@25.2.1) js-yaml: 4.1.1 ora: 4.1.1 picocolors: 1.0.1 @@ -12269,9 +12853,14 @@ snapshots: tslib: 2.8.1 optional: true + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/d3-array@3.2.2': {} @@ -12289,7 +12878,7 @@ snapshots: dependencies: '@types/d3-time': 3.0.4 - '@types/d3-shape@3.1.7': + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 @@ -12301,6 +12890,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -12320,7 +12911,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/hast@3.0.4': dependencies: @@ -12340,7 +12931,7 @@ snapshots: '@types/linkify-it@3.0.5': optional: true - '@types/lodash@4.17.20': {} + '@types/lodash@4.17.23': {} '@types/markdown-it@12.2.3': dependencies: @@ -12363,15 +12954,15 @@ snapshots: '@types/mysql@2.15.27': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 - '@types/node@25.2.1': + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 '@types/nodemailer@7.0.9': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/parse-json@4.0.2': {} @@ -12381,27 +12972,27 @@ snapshots: '@types/pg@8.15.6': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 pg-protocol: 1.11.0 pg-types: 2.2.0 '@types/phoenix@1.6.7': {} - '@types/react-dom@19.2.3(@types/react@19.2.13)': + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - '@types/react@19.2.13': + '@types/react@19.2.14': dependencies: csstype: 3.2.3 '@types/tedious@4.0.14': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/through@0.0.33': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/tinycolor2@1.4.6': {} @@ -12411,16 +13002,16 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 - '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 @@ -12429,41 +13020,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.54.0': + '@typescript-eslint/scope-manager@8.55.0': dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 - '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) @@ -12471,14 +13062,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.54.0': {} + '@typescript-eslint/types@8.55.0': {} - '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.4 @@ -12488,20 +13079,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.54.0': + '@typescript-eslint/visitor-keys@8.55.0': dependencies: - '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -12588,6 +13179,45 @@ snapshots: '@urql/core': 5.2.0(graphql@16.12.0) wonka: 6.3.5 + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -12749,13 +13379,13 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - arg@4.1.3: {} - argparse@2.0.1: {} aria-hidden@1.2.6: @@ -12833,6 +13463,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} ast-types@0.13.4: @@ -12855,20 +13487,16 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 cosmiconfig: 7.1.0 resolve: 1.22.11 babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 balanced-match@1.0.2: {} - base64-js@1.5.1: {} - - baseline-browser-mapping@2.8.28: {} - baseline-browser-mapping@2.9.19: {} basic-ftp@5.1.0: {} @@ -12883,12 +13511,6 @@ snapshots: binary-extensions@2.3.0: {} - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -12918,14 +13540,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.0: - dependencies: - baseline-browser-mapping: 2.8.28 - caniuse-lite: 1.0.30001754 - electron-to-chromium: 1.5.253 - node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.28.0) - browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.19 @@ -12936,17 +13550,19 @@ snapshots: buffer-from@1.1.2: {} - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - builtins@5.1.0: dependencies: semver: 7.7.4 + bundle-require@5.1.0(esbuild@0.27.3): + dependencies: + esbuild: 0.27.3 + load-tsconfig: 0.2.5 + bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -12973,19 +13589,17 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.28.0 - caniuse-lite: 1.0.30001757 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001769 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001754: {} - - caniuse-lite@1.0.30001757: {} - caniuse-lite@1.0.30001769: {} ccount@2.0.1: {} + chai@6.2.2: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -13047,6 +13661,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@3.0.0: {} chrome-trace-event@1.0.4: {} @@ -13067,6 +13685,8 @@ snapshots: cli-width@3.0.0: {} + cli-width@4.1.0: {} + client-only@0.0.1: {} clone@1.0.4: {} @@ -13075,12 +13695,12 @@ snapshots: cmd-shim@8.0.0: {} - cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: @@ -13109,6 +13729,8 @@ snapshots: commander@2.20.3: {} + commander@4.1.1: {} + commander@7.2.0: {} commondir@1.0.1: {} @@ -13119,11 +13741,15 @@ snapshots: concat-map@0.0.1: {} + confbox@0.1.8: {} + config-chain@1.1.13: dependencies: ini: 1.3.8 proto-list: 1.2.4 + consola@3.4.2: {} + constant-case@2.0.0: dependencies: snake-case: 2.1.0 @@ -13141,8 +13767,6 @@ snapshots: cookie@0.7.2: {} - cookie@1.0.2: {} - cookie@1.1.1: {} core-js-pure@3.48.0: {} @@ -13160,8 +13784,6 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-require@1.1.1: {} - cross-env@10.1.0: dependencies: '@epic-web/invariant': 1.0.0 @@ -13173,7 +13795,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@7.3.0(postcss@8.5.6): + css-declaration-sorter@7.3.1(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -13201,8 +13823,8 @@ snapshots: cssnano-preset-default@7.0.10(postcss@8.5.6): dependencies: - browserslist: 4.28.0 - css-declaration-sorter: 7.3.0(postcss@8.5.6) + browserslist: 4.28.1 + css-declaration-sorter: 7.3.1(postcss@8.5.6) cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 postcss-calc: 10.1.1(postcss@8.5.6) @@ -13257,7 +13879,7 @@ snapshots: d3-ease@3.0.1: {} - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-interpolate@3.0.1: dependencies: @@ -13268,7 +13890,7 @@ snapshots: d3-scale@4.0.2: dependencies: d3-array: 3.2.4 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -13331,7 +13953,7 @@ snapshots: decimal.js@10.6.0: {} - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -13388,8 +14010,6 @@ snapshots: dependencies: dequal: 2.0.3 - diff@4.0.4: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -13402,7 +14022,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 csstype: 3.2.3 dom-serializer@2.0.0: @@ -13445,8 +14065,6 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.253: {} - electron-to-chromium@1.5.286: {} emery@1.4.4: {} @@ -13461,11 +14079,6 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.4: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 @@ -13559,6 +14172,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -13580,6 +14195,35 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -13600,18 +14244,18 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-next@16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.1.6 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) globals: 16.4.0 - typescript-eslint: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -13620,11 +14264,11 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-turbo@2.8.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.7.6): + eslint-config-turbo@2.8.5(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.5): dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-turbo: 2.8.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.7.6) - turbo: 2.7.6 + eslint-plugin-turbo: 2.8.5(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.5) + turbo: 2.8.5 eslint-import-resolver-node@0.3.9: dependencies: @@ -13634,33 +14278,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - get-tsconfig: 4.13.5 + get-tsconfig: 4.13.6 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13671,7 +14315,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13683,7 +14327,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -13741,11 +14385,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.8.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.7.6): + eslint-plugin-turbo@2.8.5(eslint@9.39.2(jiti@2.6.1))(turbo@2.8.5): dependencies: dotenv: 16.0.3 eslint: 9.39.2(jiti@2.6.1) - turbo: 2.7.6 + turbo: 2.8.5 eslint-scope@5.1.1: dependencies: @@ -13783,7 +14427,7 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -13810,7 +14454,7 @@ snapshots: esprima@4.0.1: {} - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -13831,6 +14475,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} @@ -13859,6 +14507,8 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + expect-type@1.3.0: {} + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 @@ -13909,7 +14559,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-equals@5.3.3: {} + fast-equals@5.4.0: {} fast-glob@3.2.12: dependencies: @@ -13943,7 +14593,7 @@ snapshots: fast-uri@3.1.0: {} - fastq@1.19.1: + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -13990,6 +14640,12 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.0 + rollup: 4.57.1 + flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -14075,7 +14731,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.13.5: + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -14205,7 +14861,7 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.11.8: {} + hono@4.11.9: {} html-escaper@2.0.2: {} @@ -14261,13 +14917,13 @@ snapshots: i18next-browser-languagedetector@8.2.0: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 i18next-resources-to-backend@1.2.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 - i18next@25.8.4(typescript@5.9.3): + i18next@25.8.5(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.6 optionalDependencies: @@ -14287,8 +14943,6 @@ snapshots: idb@7.1.1: {} - ieee754@1.2.1: {} - ignore@5.3.2: {} ignore@7.0.5: {} @@ -14341,26 +14995,6 @@ snapshots: strip-ansi: 6.0.1 through: 2.3.8 - inquirer@8.2.7(@types/node@25.2.1): - dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@25.2.1) - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - figures: 3.2.0 - lodash: 4.17.23 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - transitivePeerDependencies: - - '@types/node' - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -14526,8 +15160,6 @@ snapshots: dependencies: which-typed-array: 1.1.20 - is-unicode-supported@0.1.0: {} - is-upper-case@1.1.2: dependencies: upper-case: 1.1.3 @@ -14570,7 +15202,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14627,7 +15259,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - ky@1.14.0: {} + ky@1.14.3: {} language-subtag-registry@0.3.23: {} @@ -14642,7 +15274,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lib0@0.2.114: + lib0@0.2.117: dependencies: isomorphic.js: 0.2.5 @@ -14699,13 +15331,15 @@ snapshots: lines-and-columns@1.2.4: {} + load-tsconfig@0.2.5: {} + loader-runner@4.3.1: {} locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash-es@4.17.22: {} + lodash-es@4.17.23: {} lodash.deburr@4.1.0: {} @@ -14717,19 +15351,12 @@ snapshots: lodash.uniq@4.5.0: {} - lodash@4.17.21: {} - lodash@4.17.23: {} log-symbols@3.0.0: dependencies: chalk: 2.4.2 - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -14762,15 +15389,13 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-error@1.3.6: {} - markdown-table@3.0.4: {} marked@15.0.12: {} match-sorter@6.3.4: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 remove-accents: 0.5.0 math-intrinsics@1.1.0: {} @@ -14786,7 +15411,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -14888,7 +15513,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -14909,7 +15534,7 @@ snapshots: micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -15071,7 +15696,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -15119,7 +15744,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -15180,6 +15805,13 @@ snapshots: dependencies: minimist: 1.2.8 + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + module-details-from-path@1.0.4: {} mrmime@2.0.1: {} @@ -15188,6 +15820,14 @@ snapshots: mute-stream@0.0.8: {} + mute-stream@2.0.0: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.11: {} nanoid@5.1.6: {} @@ -15202,20 +15842,20 @@ snapshots: netmask@2.0.2: {} - next-sitemap@4.2.3(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + next-sitemap@4.2.3(next@16.1.6(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.11 fast-glob: 3.3.3 minimist: 1.2.8 - next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -15235,7 +15875,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.1.6 '@next/swc-win32-x64-msvc': 16.1.6 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.58.1 + '@playwright/test': 1.58.2 babel-plugin-react-compiler: 1.0.0 sharp: 0.34.5 transitivePeerDependencies: @@ -15279,7 +15919,7 @@ snapshots: node-releases@2.0.27: {} - nodemailer@8.0.0: {} + nodemailer@8.0.1: {} normalize-path@3.0.0: {} @@ -15337,6 +15977,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -15373,18 +16015,6 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - orderedmap@2.1.1: {} os-tmpdir@1.0.2: {} @@ -15433,10 +16063,10 @@ snapshots: package-json@10.0.1: dependencies: - ky: 1.14.0 - registry-auth-token: 5.1.0 + ky: 1.14.3 + registry-auth-token: 5.1.1 registry-url: 6.0.1 - semver: 7.7.3 + semver: 7.7.4 param-case@2.1.1: dependencies: @@ -15451,7 +16081,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -15464,7 +16094,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -15508,6 +16138,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + peberminta@0.9.0: {} pg-int8@1.0.1: {} @@ -15551,12 +16183,12 @@ snapshots: pino-abstract-transport: 2.0.0 pump: 3.0.3 secure-json-parse: 2.7.0 - sonic-boom: 4.2.0 + sonic-boom: 4.2.1 strip-json-comments: 3.1.1 pino-std-serializers@7.1.0: {} - pino@10.3.0: + pino@10.3.1: dependencies: '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 @@ -15567,16 +16199,24 @@ snapshots: quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 + sonic-boom: 4.2.1 thread-stream: 4.0.0 + pirates@4.0.7: {} + pkce-challenge@5.0.1: {} - playwright-core@1.58.1: {} - - playwright@1.58.1: + pkg-types@1.3.1: dependencies: - playwright-core: 1.58.1 + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 optionalDependencies: fsevents: 2.3.2 @@ -15585,12 +16225,12 @@ snapshots: postcss-calc@10.1.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-colormin@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.5.6 @@ -15598,14 +16238,14 @@ snapshots: postcss-convert-values@7.0.8(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 postcss-discard-comments@7.0.5(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-discard-duplicates@7.0.2(postcss@8.5.6): dependencies: @@ -15619,6 +16259,14 @@ snapshots: dependencies: postcss: 8.5.6 + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.6.1 + postcss: 8.5.6 + tsx: 4.21.0 + postcss-merge-longhand@7.0.5(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -15627,11 +16275,11 @@ snapshots: postcss-merge-rules@7.0.7(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-minify-font-values@7.0.1(postcss@8.5.6): dependencies: @@ -15647,7 +16295,7 @@ snapshots: postcss-minify-params@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -15656,7 +16304,7 @@ snapshots: dependencies: cssesc: 3.0.0 postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-normalize-charset@7.0.1(postcss@8.5.6): dependencies: @@ -15689,7 +16337,7 @@ snapshots: postcss-normalize-unicode@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -15711,7 +16359,7 @@ snapshots: postcss-reduce-initial@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 caniuse-api: 3.0.0 postcss: 8.5.6 @@ -15720,7 +16368,7 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-selector-parser@7.1.0: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -15734,7 +16382,7 @@ snapshots: postcss-unique-selectors@7.0.4(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser@4.2.0: {} @@ -15790,13 +16438,13 @@ snapshots: dependencies: prosemirror-model: 1.25.4 prosemirror-state: 1.4.4 - prosemirror-transform: 1.10.5 + prosemirror-transform: 1.11.0 prosemirror-history@1.5.0: dependencies: prosemirror-state: 1.4.4 - prosemirror-transform: 1.10.5 - prosemirror-view: 1.41.3 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 rope-sequence: 1.3.4 prosemirror-keymap@1.2.3: @@ -15811,26 +16459,26 @@ snapshots: prosemirror-state@1.4.4: dependencies: prosemirror-model: 1.25.4 - prosemirror-transform: 1.10.5 - prosemirror-view: 1.41.3 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 - prosemirror-tables@1.8.1: + prosemirror-tables@1.8.5: dependencies: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.4 prosemirror-state: 1.4.4 - prosemirror-transform: 1.10.5 - prosemirror-view: 1.41.3 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 - prosemirror-transform@1.10.5: + prosemirror-transform@1.11.0: dependencies: prosemirror-model: 1.25.4 - prosemirror-view@1.41.3: + prosemirror-view@1.41.6: dependencies: prosemirror-model: 1.25.4 prosemirror-state: 1.4.4 - prosemirror-transform: 1.10.5 + prosemirror-transform: 1.11.0 proto-list@1.2.4: {} @@ -15869,68 +16517,68 @@ snapshots: quick-format-unescaped@4.0.4: {} - radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.13)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 - '@types/react-dom': 19.2.3(@types/react@19.2.13) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) randombytes@2.1.0: dependencies: @@ -15952,7 +16600,7 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-day-picker@9.13.0(react@19.2.4): + react-day-picker@9.13.2(react@19.2.4): dependencies: '@date-fns/tz': 1.4.1 date-fns: 4.1.0 @@ -15964,7 +16612,7 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 - react-dropzone@14.4.0(react@19.2.4): + react-dropzone@14.4.1(react@19.2.4): dependencies: attr-accept: 2.2.5 file-selector: 2.1.2 @@ -15975,11 +16623,11 @@ snapshots: dependencies: react: 19.2.4 - react-i18next@16.5.4(i18next@25.8.4(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.5.4(i18next@25.8.5(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.6 html-parse-stringify: 3.0.1 - i18next: 25.8.4(typescript@5.9.3) + i18next: 25.8.5(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: @@ -15990,40 +16638,40 @@ snapshots: react-is@18.3.1: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.13)(react@19.2.4): + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@19.2.13)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - react-remove-scroll@2.7.1(@types/react@19.2.13)(react@19.2.4): + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.13)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@19.2.13)(react@19.2.4) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.13)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@19.2.13)(react@19.2.4) + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 react-smooth@4.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - fast-equals: 5.3.3 + fast-equals: 5.4.0 prop-types: 15.8.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-style-singleton@2.2.3(@types/react@19.2.13)(react@19.2.4): + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4): dependencies: get-nonce: 1.0.1 react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 react-top-loading-bar@3.0.2(react@19.2.4): dependencies: @@ -16031,7 +16679,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -16042,16 +16690,12 @@ snapshots: read-cmd-shim@6.0.0: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + real-require@0.2.0: {} recharts-scale@0.4.5: @@ -16062,7 +16706,7 @@ snapshots: dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 - lodash: 4.17.21 + lodash: 4.17.23 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) react-is: 18.3.1 @@ -16096,9 +16740,9 @@ snapshots: rc: 1.2.8 safe-buffer: 5.2.1 - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: dependencies: - '@pnpm/npm-conf': 2.3.1 + '@pnpm/npm-conf': 3.0.2 registry-url@3.1.0: dependencies: @@ -16121,6 +16765,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: @@ -16228,7 +16874,7 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.4.3: {} + sax@1.4.4: {} scheduler@0.27.0: {} @@ -16255,14 +16901,12 @@ snapshots: sembear@0.7.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 semver@6.3.1: {} semver@7.6.2: {} - semver@7.7.3: {} - semver@7.7.4: {} send@1.2.1: @@ -16391,6 +17035,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -16412,11 +17058,11 @@ snapshots: dependencies: '@juggle/resize-observer': 3.4.0 '@types/is-hotkey': 0.1.10 - '@types/lodash': 4.17.20 + '@types/lodash': 4.17.23 direction: 1.0.4 is-hotkey: 0.1.8 is-plain-object: 5.0.0 - lodash: 4.17.21 + lodash: 4.17.23 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) scroll-into-view-if-needed: 2.2.31 @@ -16448,7 +17094,7 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 - sonic-boom@4.2.0: + sonic-boom@4.2.1: dependencies: atomic-sleep: 1.0.0 @@ -16468,16 +17114,22 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.6: {} + split2@4.2.0: {} stable-hash@0.0.5: {} + stackback@0.0.2: {} + stacktrace-parser@0.1.11: dependencies: type-fest: 0.7.1 statuses@2.0.2: {} + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -16545,10 +17197,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -16570,9 +17218,9 @@ snapshots: strip-json-comments@3.1.1: {} - stripe@20.3.1(@types/node@25.2.1): + stripe@20.3.1(@types/node@25.2.3): optionalDependencies: - '@types/node': 25.2.1 + '@types/node': 25.2.3 styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4): dependencies: @@ -16583,13 +17231,23 @@ snapshots: stylehacks@7.0.7(postcss@8.5.6): dependencies: - browserslist: 4.28.0 + browserslist: 4.28.1 postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 stylis@4.2.0: {} - supabase@2.75.5: + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supabase@2.76.7: dependencies: bin-links: 6.0.0 https-proxy-agent: 7.0.6 @@ -16622,14 +17280,14 @@ snapshots: css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.3 + sax: 1.4.4 swap-case@1.1.2: dependencies: lower-case: 1.1.4 upper-case: 1.1.3 - tabbable@6.3.0: {} + tabbable@6.4.0: {} tailwind-merge@3.4.0: {} @@ -16649,14 +17307,14 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - terser-webpack-plugin@5.3.16(webpack@5.102.1): + terser-webpack-plugin@5.3.16(webpack@5.105.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.46.0 - webpack: 5.102.1 + webpack: 5.105.1 terser@5.46.0: dependencies: @@ -16665,6 +17323,14 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thread-stream@4.0.0: dependencies: real-require: 0.2.0 @@ -16677,8 +17343,12 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -16691,6 +17361,8 @@ snapshots: '@types/tinycolor2': 1.4.6 tinycolor2: 1.6.0 + tinyrainbow@3.0.3: {} + title-case@2.1.1: dependencies: no-case: 2.3.2 @@ -16714,29 +17386,15 @@ snapshots: tr46@0.0.3: {} + tree-kill@1.2.2: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 ts-case-convert@2.1.0: {} - ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 25.2.1 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + ts-interface-checker@0.1.13: {} tsconfig-paths@3.15.0: dependencies: @@ -16749,32 +17407,67 @@ snapshots: tslib@2.8.1: {} - turbo-darwin-64@2.7.6: - optional: true - - turbo-darwin-arm64@2.7.6: - optional: true - - turbo-linux-64@2.7.6: - optional: true - - turbo-linux-arm64@2.7.6: - optional: true - - turbo-windows-64@2.7.6: - optional: true - - turbo-windows-arm64@2.7.6: - optional: true - - turbo@2.7.6: + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.3) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.3 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0) + resolve-from: 5.0.0 + rollup: 4.57.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 optionalDependencies: - turbo-darwin-64: 2.7.6 - turbo-darwin-arm64: 2.7.6 - turbo-linux-64: 2.7.6 - turbo-linux-arm64: 2.7.6 - turbo-windows-64: 2.7.6 - turbo-windows-arm64: 2.7.6 + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + + turbo-darwin-64@2.8.5: + optional: true + + turbo-darwin-arm64@2.8.5: + optional: true + + turbo-linux-64@2.8.5: + optional: true + + turbo-linux-arm64@2.8.5: + optional: true + + turbo-windows-64@2.8.5: + optional: true + + turbo-windows-arm64@2.8.5: + optional: true + + turbo@2.8.5: + optionalDependencies: + turbo-darwin-64: 2.8.5 + turbo-darwin-arm64: 2.8.5 + turbo-linux-64: 2.8.5 + turbo-linux-arm64: 2.8.5 + turbo-windows-64: 2.8.5 + turbo-windows-arm64: 2.8.5 tw-animate-css@1.4.0: {} @@ -16825,12 +17518,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -16838,6 +17531,8 @@ snapshots: typescript@5.9.3: {} + ufo@1.6.3: {} + uglify-js@3.19.3: optional: true @@ -16867,7 +17562,7 @@ snapshots: '@types/unist': 3.0.3 unist-util-is: 6.0.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 @@ -16908,12 +17603,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.4(browserslist@4.28.0): - dependencies: - browserslist: 4.28.0 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -16943,20 +17632,20 @@ snapshots: react: 19.2.4 wonka: 6.3.5 - use-callback-ref@1.3.3(@types/react@19.2.13)(react@19.2.4): + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 - use-sidecar@1.1.3(@types/react@19.2.13)(react@19.2.4): + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): dependencies: detect-node-es: 1.1.0 react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.13 + '@types/react': 19.2.14 use-sync-external-store@1.6.0(react@19.2.4): dependencies: @@ -16966,8 +17655,6 @@ snapshots: uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: {} - validate-npm-package-name@5.0.0: dependencies: builtins: 5.1.0 @@ -16987,7 +17674,7 @@ snapshots: '@types/d3-ease': 3.0.2 '@types/d3-interpolate': 3.0.4 '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.7 + '@types/d3-shape': 3.1.8 '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 d3-array: 3.2.4 @@ -16998,6 +17685,60 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.2.3 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + terser: 5.46.0 + tsx: 4.21.0 + + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 25.2.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + void-elements@3.1.0: {} w3c-keyname@2.2.8: {} @@ -17038,7 +17779,7 @@ snapshots: webpack-virtual-modules@0.5.0: {} - webpack@5.102.1: + webpack@5.105.1: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -17051,7 +17792,7 @@ snapshots: browserslist: 4.28.1 chrome-trace-event: 1.0.4 enhanced-resolve: 5.19.0 - es-module-lexer: 1.7.0 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -17062,7 +17803,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.102.1) + terser-webpack-plugin: 5.3.16(webpack@5.105.1) watchpack: 2.5.1 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -17120,6 +17861,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wonka@6.3.5: {} word-wrap@1.2.5: {} @@ -17161,23 +17907,23 @@ snapshots: xtend@4.0.2: {} - y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27): + y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29): dependencies: - lib0: 0.2.114 + lib0: 0.2.117 prosemirror-model: 1.25.4 prosemirror-state: 1.4.4 - prosemirror-view: 1.41.3 - y-protocols: 1.0.6(yjs@13.6.27) - yjs: 13.6.27 + prosemirror-view: 1.41.6 + y-protocols: 1.0.7(yjs@13.6.29) + yjs: 13.6.29 - y-protocols@1.0.6(yjs@13.6.27): + y-protocols@1.0.7(yjs@13.6.29): dependencies: - lib0: 0.2.114 - yjs: 13.6.27 + lib0: 0.2.117 + yjs: 13.6.29 - y-provider@0.10.0-canary.9(yjs@13.6.27): + y-provider@0.10.0-canary.9(yjs@13.6.29): dependencies: - yjs: 13.6.27 + yjs: 13.6.29 yallist@3.1.1: {} @@ -17185,16 +17931,16 @@ snapshots: yaml@1.10.2: {} - yjs@13.6.27: + yjs@13.6.29: dependencies: - lib0: 0.2.114 - - yn@3.1.1: {} + lib0: 0.2.117 yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} + yoctocolors-cjs@2.1.3: {} + zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7793f1472..68556c1a0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,28 +13,30 @@ catalog: '@stripe/stripe-js': 8.7.0 '@supabase/supabase-js': 2.95.3 '@tailwindcss/postcss': 4.1.18 - '@tanstack/react-query': 5.90.20 + '@tanstack/react-query': 5.90.21 '@types/eslint': 9.6.1 - '@types/node': 25.2.1 + '@types/node': 25.2.3 '@types/nodemailer': 7.0.9 - '@types/react': 19.2.13 + '@types/react': 19.2.14 '@types/react-dom': 19.2.3 eslint: 9.39.2 eslint-config-next: 16.1.6 - eslint-config-turbo: 2.8.3 - i18next: 25.8.4 + eslint-config-turbo: 2.8.5 + i18next: 25.8.5 i18next-browser-languagedetector: 8.2.0 i18next-resources-to-backend: 1.2.1 lucide-react: 0.563.0 next: 16.1.6 - nodemailer: 8.0.0 + nodemailer: 8.0.1 + pino: 10.3.1 react: 19.2.4 react-dom: 19.2.4 react-hook-form: 7.71.1 react-i18next: 16.5.4 stripe: 20.3.1 - supabase: 2.75.5 + supabase: 2.76.7 tailwindcss: 4.1.18 + tsup: 8.5.1 tw-animate-css: 1.4.0 zod: 3.25.76