diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..d4f16c2 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.turbo/ +*.tsbuildinfo +coverage/ +.env +.env.local diff --git a/frontend/apps/cli/package.json b/frontend/apps/cli/package.json new file mode 100644 index 0000000..4b4fd1a --- /dev/null +++ b/frontend/apps/cli/package.json @@ -0,0 +1,47 @@ +{ + "name": "@effigenix/cli", + "version": "0.1.0", + "description": "Terminal User Interface for Effigenix ERP", + "type": "module", + "bin": { + "effigenix": "./dist/index.js" + }, + "scripts": { + "build": "tsup", + "dev": "tsx src/index.tsx", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "clean": "rm -rf dist .turbo" + }, + "dependencies": { + "@effigenix/api-client": "workspace:*", + "@effigenix/config": "workspace:*", + "@effigenix/types": "workspace:*", + "@effigenix/validation": "workspace:*", + "ink": "^5.0.1", + "ink-text-input": "^6.0.0", + "react": "^18.2.0", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@types/react": "^18.2.0", + "@types/yargs": "^17.0.32", + "ink-testing-library": "^4.0.0", + "tsup": "^8.0.1", + "tsx": "^4.7.0", + "typescript": "^5.3.3", + "vitest": "^1.2.0" + }, + "tsup": { + "entry": ["src/index.tsx"], + "format": ["esm"], + "dts": false, + "clean": true, + "sourcemap": true, + "splitting": false, + "banner": { + "js": "#!/usr/bin/env node" + } + } +} diff --git a/frontend/apps/cli/src/App.tsx b/frontend/apps/cli/src/App.tsx new file mode 100644 index 0000000..f536447 --- /dev/null +++ b/frontend/apps/cli/src/App.tsx @@ -0,0 +1,64 @@ +import React, { useEffect } from 'react'; +import { Box } from 'ink'; +import { AuthProvider, useAuth } from './state/auth-context.js'; +import { NavigationProvider, useNavigation } from './state/navigation-context.js'; +import { LoginScreen } from './components/auth/LoginScreen.js'; +import { MainMenu } from './components/MainMenu.js'; +import { MainLayout } from './components/layout/MainLayout.js'; +import { LoadingSpinner } from './components/shared/LoadingSpinner.js'; +import { UserListScreen } from './components/users/UserListScreen.js'; +import { UserCreateScreen } from './components/users/UserCreateScreen.js'; +import { UserDetailScreen } from './components/users/UserDetailScreen.js'; +import { ChangePasswordScreen } from './components/users/ChangePasswordScreen.js'; +import { RoleListScreen } from './components/roles/RoleListScreen.js'; +import { RoleDetailScreen } from './components/roles/RoleDetailScreen.js'; + +function ScreenRouter() { + const { isAuthenticated, loading } = useAuth(); + const { current, navigate } = useNavigation(); + + // Redirect zu main-menu nach erfolgreichem Login + useEffect(() => { + if (isAuthenticated && current === 'login') { + navigate('main-menu'); + } + }, [isAuthenticated, current, navigate]); + + if (loading) { + return ( + + + + ); + } + + if (!isAuthenticated) { + return ( + + + + ); + } + + return ( + + {current === 'main-menu' && } + {current === 'user-list' && } + {current === 'user-create' && } + {current === 'user-detail' && } + {current === 'change-password' && } + {current === 'role-list' && } + {current === 'role-detail' && } + + ); +} + +export function App() { + return ( + + + + + + ); +} diff --git a/frontend/apps/cli/src/__tests__/hooks/useRoles.test.ts b/frontend/apps/cli/src/__tests__/hooks/useRoles.test.ts new file mode 100644 index 0000000..510d21d --- /dev/null +++ b/frontend/apps/cli/src/__tests__/hooks/useRoles.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import type { RoleDTO } from '@effigenix/api-client'; + +vi.mock('../../utils/api-client.js', () => ({ + client: { + roles: { + list: vi.fn(), + }, + }, +})); + +const { client } = await import('../../utils/api-client.js'); + +const mockRoles: RoleDTO[] = [ + { id: '1', name: 'ADMIN', permissions: [] }, + { id: '2', name: 'USER', permissions: [] }, +]; + +describe('useRoles – api-client integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('client.roles.list resolves with roles', async () => { + vi.mocked(client.roles.list).mockResolvedValue(mockRoles); + const result = await client.roles.list(); + expect(result).toEqual(mockRoles); + }); + + it('client.roles.list rejects with error', async () => { + vi.mocked(client.roles.list).mockRejectedValue(new Error('Netzwerkfehler')); + await expect(client.roles.list()).rejects.toThrow('Netzwerkfehler'); + }); + + it('error message extraction from Error instance', () => { + const err: unknown = new Error('Verbindungsfehler'); + const msg = err instanceof Error ? err.message : 'Unbekannter Fehler'; + expect(msg).toBe('Verbindungsfehler'); + }); + + it('error message fallback for non-Error', () => { + const err: unknown = 'string error'; + const msg = err instanceof Error ? err.message : 'Unbekannter Fehler'; + expect(msg).toBe('Unbekannter Fehler'); + }); +}); diff --git a/frontend/apps/cli/src/__tests__/shared/ConfirmDialog.test.tsx b/frontend/apps/cli/src/__tests__/shared/ConfirmDialog.test.tsx new file mode 100644 index 0000000..93f2bc2 --- /dev/null +++ b/frontend/apps/cli/src/__tests__/shared/ConfirmDialog.test.tsx @@ -0,0 +1,40 @@ +import { describe, expect, it, vi } from 'vitest'; +import { render } from 'ink-testing-library'; +import React from 'react'; +import { ConfirmDialog } from '../../components/shared/ConfirmDialog.js'; + +describe('ConfirmDialog', () => { + it('renders the message', () => { + const { lastFrame } = render( + React.createElement(ConfirmDialog, { + message: 'Benutzer sperren?', + onConfirm: vi.fn(), + onCancel: vi.fn(), + }), + ); + expect(lastFrame()).toContain('Benutzer sperren?'); + }); + + it('renders J/N hint', () => { + const { lastFrame } = render( + React.createElement(ConfirmDialog, { + message: 'Test?', + onConfirm: vi.fn(), + onCancel: vi.fn(), + }), + ); + expect(lastFrame()).toContain('[J]'); + expect(lastFrame()).toContain('[N]'); + }); + + it('renders Bestätigung label', () => { + const { lastFrame } = render( + React.createElement(ConfirmDialog, { + message: 'Aktion bestätigen?', + onConfirm: vi.fn(), + onCancel: vi.fn(), + }), + ); + expect(lastFrame()).toContain('Bestätigung'); + }); +}); diff --git a/frontend/apps/cli/src/__tests__/shared/ErrorDisplay.test.tsx b/frontend/apps/cli/src/__tests__/shared/ErrorDisplay.test.tsx new file mode 100644 index 0000000..1b66e8d --- /dev/null +++ b/frontend/apps/cli/src/__tests__/shared/ErrorDisplay.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { render } from 'ink-testing-library'; +import React from 'react'; +import { ErrorDisplay } from '../../components/shared/ErrorDisplay.js'; + +describe('ErrorDisplay', () => { + it('renders the error message', () => { + const { lastFrame } = render(React.createElement(ErrorDisplay, { message: 'Test-Fehler' })); + expect(lastFrame()).toContain('Test-Fehler'); + }); + + it('renders without onDismiss without throwing', () => { + expect(() => render(React.createElement(ErrorDisplay, { message: 'Fehler' }))).not.toThrow(); + }); + + it('does not render dismiss hint when onDismiss is absent', () => { + const { lastFrame } = render(React.createElement(ErrorDisplay, { message: 'Fehler' })); + expect(lastFrame()).not.toContain('Schließen'); + }); + + it('renders dismiss hint when onDismiss is provided', () => { + const { lastFrame } = render( + React.createElement(ErrorDisplay, { message: 'Fehler', onDismiss: () => undefined }), + ); + expect(lastFrame()).toContain('Schließen'); + }); +}); diff --git a/frontend/apps/cli/src/__tests__/shared/SuccessDisplay.test.tsx b/frontend/apps/cli/src/__tests__/shared/SuccessDisplay.test.tsx new file mode 100644 index 0000000..a8ed296 --- /dev/null +++ b/frontend/apps/cli/src/__tests__/shared/SuccessDisplay.test.tsx @@ -0,0 +1,53 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { render } from 'ink-testing-library'; +import React, { act } from 'react'; +import { SuccessDisplay } from '../../components/shared/SuccessDisplay.js'; + +describe('SuccessDisplay', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('renders the success message', () => { + const onDismiss = vi.fn(); + const { lastFrame } = render( + React.createElement(SuccessDisplay, { message: 'Aktion erfolgreich.', onDismiss }), + ); + expect(lastFrame()).toContain('Aktion erfolgreich.'); + }); + + it('renders with green Erfolg label', () => { + const onDismiss = vi.fn(); + const { lastFrame } = render( + React.createElement(SuccessDisplay, { message: 'Ok', onDismiss }), + ); + expect(lastFrame()).toContain('Erfolg'); + }); + + it('calls onDismiss after 3 seconds', async () => { + const onDismiss = vi.fn(); + await act(async () => { + render(React.createElement(SuccessDisplay, { message: 'Ok', onDismiss })); + }); + expect(onDismiss).not.toHaveBeenCalled(); + await act(async () => { + vi.advanceTimersByTime(3000); + }); + expect(onDismiss).toHaveBeenCalledOnce(); + }); + + it('does not call onDismiss before 3 seconds', async () => { + const onDismiss = vi.fn(); + await act(async () => { + render(React.createElement(SuccessDisplay, { message: 'Ok', onDismiss })); + }); + await act(async () => { + vi.advanceTimersByTime(2999); + }); + expect(onDismiss).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/apps/cli/src/components/MainMenu.tsx b/frontend/apps/cli/src/components/MainMenu.tsx new file mode 100644 index 0000000..10a2358 --- /dev/null +++ b/frontend/apps/cli/src/components/MainMenu.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useAuth } from '../state/auth-context.js'; +import { useNavigation } from '../state/navigation-context.js'; +import type { Screen } from '../state/navigation-context.js'; + +interface MenuItem { + label: string; + screen?: Screen; + action?: () => void | Promise; +} + +export function MainMenu() { + const { user, logout } = useAuth(); + const { navigate } = useNavigation(); + const [selectedIndex, setSelectedIndex] = useState(0); + + const items: MenuItem[] = [ + { label: 'Benutzer verwalten', screen: 'user-list' }, + { label: 'Rollen anzeigen', screen: 'role-list' }, + { label: 'Abmelden', action: () => void logout() }, + ]; + + useInput((_input, key) => { + if (key.upArrow) { + setSelectedIndex((i) => (i > 0 ? i - 1 : items.length - 1)); + } + if (key.downArrow) { + setSelectedIndex((i) => (i < items.length - 1 ? i + 1 : 0)); + } + if (key.return) { + const item = items[selectedIndex]; + if (item) { + if (item.screen) { + navigate(item.screen); + } else if (item.action) { + void item.action(); + } + } + } + }); + + return ( + + + Willkommen, + + {user?.username} + + + + + + Hauptmenü + + + {items.map((item, index) => ( + + + {index === selectedIndex ? '▶ ' : ' '} + {item.label} + + + ))} + + + + ↑↓ navigieren · Enter auswählen + + + + + ); +} diff --git a/frontend/apps/cli/src/components/auth/LoginScreen.tsx b/frontend/apps/cli/src/components/auth/LoginScreen.tsx new file mode 100644 index 0000000..2c43eb0 --- /dev/null +++ b/frontend/apps/cli/src/components/auth/LoginScreen.tsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import TextInput from 'ink-text-input'; +import { useAuth } from '../../state/auth-context.js'; +import { useNavigation } from '../../state/navigation-context.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; + +type Field = 'username' | 'password'; + +export function LoginScreen() { + const { login, loading, error, clearError } = useAuth(); + const { navigate } = useNavigation(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [activeField, setActiveField] = useState('username'); + + useInput((input, key) => { + if (loading) return; + + if (key.tab || key.downArrow) { + setActiveField((f) => (f === 'username' ? 'password' : 'username')); + } + if (key.upArrow) { + setActiveField((f) => (f === 'password' ? 'username' : 'password')); + } + if (key.escape && error) { + clearError(); + } + void input; // suppress unused warning + }); + + const handleUsernameSubmit = (value: string) => { + setUsername(value); + setActiveField('password'); + }; + + const handlePasswordSubmit = async (value: string) => { + setPassword(value); + if (!username.trim() || !value.trim()) return; + const success = await login(username, value); + if (success) { + navigate('main-menu'); + } + }; + + if (loading) { + return ( + + + + ); + } + + return ( + + + + + Effigenix ERP – Anmeldung + + + + {error && ( + + + + )} + + + + Benutzername + + + + + + + + Passwort + + + void handlePasswordSubmit(v)} + focus={activeField === 'password'} + mask="*" + placeholder="••••••••" + /> + + + + + + + Tab/↑↓ wechseln · Enter bestätigen + + + + + ); +} diff --git a/frontend/apps/cli/src/components/layout/Header.tsx b/frontend/apps/cli/src/components/layout/Header.tsx new file mode 100644 index 0000000..a4613a4 --- /dev/null +++ b/frontend/apps/cli/src/components/layout/Header.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Box, Text } from 'ink'; +import { useAuth } from '../../state/auth-context.js'; + +export function Header() { + const { user } = useAuth(); + + return ( + + + Effigenix ERP + + {user && ( + + »{' '}{user.username} + + )} + + ); +} diff --git a/frontend/apps/cli/src/components/layout/MainLayout.tsx b/frontend/apps/cli/src/components/layout/MainLayout.tsx new file mode 100644 index 0000000..f17f9b2 --- /dev/null +++ b/frontend/apps/cli/src/components/layout/MainLayout.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Box } from 'ink'; +import { Header } from './Header.js'; +import { StatusBar } from './StatusBar.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; + +// Header: double border (top + bottom) + 1 content line = 3 rows +const HEADER_HEIGHT = 3; +const STATUSBAR_HEIGHT = 1; + +interface MainLayoutProps { + children: React.ReactNode; + showHeader?: boolean; +} + +export function MainLayout({ children, showHeader = true }: MainLayoutProps) { + const { columns, rows } = useTerminalSize(); + const contentHeight = rows - (showHeader ? HEADER_HEIGHT : 0) - STATUSBAR_HEIGHT; + + return ( + + {showHeader &&
} + + {children} + + + + ); +} diff --git a/frontend/apps/cli/src/components/layout/StatusBar.tsx b/frontend/apps/cli/src/components/layout/StatusBar.tsx new file mode 100644 index 0000000..a60d1d6 --- /dev/null +++ b/frontend/apps/cli/src/components/layout/StatusBar.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Box, Text } from 'ink'; +import { DEFAULT_API_CONFIG } from '@effigenix/config'; + +export function StatusBar() { + return ( + + + API: {DEFAULT_API_CONFIG.baseUrl} + + + ); +} diff --git a/frontend/apps/cli/src/components/roles/RoleDetailScreen.tsx b/frontend/apps/cli/src/components/roles/RoleDetailScreen.tsx new file mode 100644 index 0000000..908a932 --- /dev/null +++ b/frontend/apps/cli/src/components/roles/RoleDetailScreen.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import type { RoleDTO } from '@effigenix/api-client'; +import { useNavigation } from '../../state/navigation-context.js'; +import { client } from '../../utils/api-client.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; + +export function RoleDetailScreen() { + const { params, back } = useNavigation(); + const roleId = params['roleId'] ?? ''; + + const [role, setRole] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!roleId) return; + + setLoading(true); + setError(null); + + client.roles + .list() + .then((roles) => { + const found = roles.find((r) => r.id === roleId) ?? null; + setRole(found); + if (!found) setError('Rolle nicht gefunden.'); + }) + .catch((err: unknown) => { + setError(err instanceof Error ? err.message : 'Unbekannter Fehler'); + }) + .finally(() => { + setLoading(false); + }); + }, [roleId]); + + useInput((_input, key) => { + if (loading) return; + if (key.backspace || key.escape) { + back(); + } + }); + + if (loading) { + return ; + } + + if (error) { + return back()} />; + } + + if (!role) { + return Rolle nicht gefunden.; + } + + return ( + + + Rollendetails + + + {/* Role Info */} + + + ID: + {role.id} + + + Name: + {role.name} + + + Berechtigungen: + {role.permissions.length} + + + + {/* Permissions List */} + {role.permissions.length > 0 && ( + + + Berechtigungen: + + + {role.permissions.map((perm) => ( + + • {perm} + + ))} + + + )} + + {role.permissions.length === 0 && ( + + Keine Berechtigungen zugewiesen. + + )} + + + + Backspace / Escape Zurück + + + + ); +} diff --git a/frontend/apps/cli/src/components/roles/RoleListScreen.tsx b/frontend/apps/cli/src/components/roles/RoleListScreen.tsx new file mode 100644 index 0000000..3450ef3 --- /dev/null +++ b/frontend/apps/cli/src/components/roles/RoleListScreen.tsx @@ -0,0 +1,66 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { useRoles } from '../../hooks/useRoles.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { RoleTable } from './RoleTable.js'; + +export function RoleListScreen() { + const { navigate, back } = useNavigation(); + const { roles, loading, error, fetchRoles, clearError } = useRoles(); + const [selectedIndex, setSelectedIndex] = useState(0); + + useEffect(() => { + void fetchRoles(); + }, [fetchRoles]); + + useInput((_input, key) => { + if (loading) return; + + if (key.upArrow) { + setSelectedIndex((i) => Math.max(0, i - 1)); + } + if (key.downArrow) { + setSelectedIndex((i) => Math.min(roles.length - 1, i + 1)); + } + if (key.return && roles.length > 0) { + const role = roles[selectedIndex]; + if (role) { + navigate('role-detail', { roleId: role.id }); + } + } + if (key.backspace || key.escape) { + back(); + } + }); + + return ( + + + + Rollenverwaltung + + + {' '}– {roles.length} Rollen + + + + {loading && } + + {error && !loading && ( + + )} + + {!loading && !error && ( + + )} + + + + ↑↓ navigieren · Enter Details · Backspace Zurück + + + + ); +} diff --git a/frontend/apps/cli/src/components/roles/RoleSelectList.tsx b/frontend/apps/cli/src/components/roles/RoleSelectList.tsx new file mode 100644 index 0000000..27df1d1 --- /dev/null +++ b/frontend/apps/cli/src/components/roles/RoleSelectList.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import type { RoleDTO } from '@effigenix/api-client'; + +interface RoleSelectListProps { + roles: RoleDTO[]; + label: string; + onSelect: (role: RoleDTO) => void; + onCancel: () => void; +} + +export function RoleSelectList({ roles, label, onSelect, onCancel }: RoleSelectListProps) { + const [selectedIndex, setSelectedIndex] = useState(0); + + useInput((_input, key) => { + if (roles.length === 0) { + if (key.escape) onCancel(); + return; + } + if (key.upArrow) { + setSelectedIndex((i) => Math.max(0, i - 1)); + } else if (key.downArrow) { + setSelectedIndex((i) => Math.min(roles.length - 1, i + 1)); + } else if (key.return) { + const role = roles[selectedIndex]; + if (role !== undefined) onSelect(role); + } else if (key.escape) { + onCancel(); + } + }); + + return ( + + + {label} + + {roles.length === 0 ? ( + + Keine Rollen verfügbar. + + ) : ( + roles.map((role, index) => ( + + {index === selectedIndex ? '▶ ' : ' '} + {role.name} + + )) + )} + + ↑↓ navigieren · Enter auswählen · Escape abbrechen + + + ); +} diff --git a/frontend/apps/cli/src/components/roles/RoleTable.tsx b/frontend/apps/cli/src/components/roles/RoleTable.tsx new file mode 100644 index 0000000..f4f8269 --- /dev/null +++ b/frontend/apps/cli/src/components/roles/RoleTable.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Box, Text } from 'ink'; +import type { RoleDTO } from '@effigenix/api-client'; + +interface RoleTableProps { + roles: RoleDTO[]; + selectedIndex: number; +} + +export function RoleTable({ roles, selectedIndex }: RoleTableProps) { + if (roles.length === 0) { + return Keine Rollen vorhanden.; + } + + return ( + + {/* Header */} + + + {' '} + {'#'.padEnd(3)} + {'Rollenname'.padEnd(25)} + {'Berechtigungen'} + + + + {'─'.repeat(70)} + + + {/* Rows */} + {roles.map((role, index) => { + const isSelected = index === selectedIndex; + const rowColor = isSelected ? 'cyan' : 'white'; + const prefix = isSelected ? '▶ ' : ' '; + const permCount = `${role.permissions.length} Berecht.`; + + return ( + + + {prefix} + {String(index + 1).padEnd(3)} + {role.name.padEnd(25)} + {permCount} + + + ); + })} + + ); +} diff --git a/frontend/apps/cli/src/components/shared/ConfirmDialog.tsx b/frontend/apps/cli/src/components/shared/ConfirmDialog.tsx new file mode 100644 index 0000000..e9b6048 --- /dev/null +++ b/frontend/apps/cli/src/components/shared/ConfirmDialog.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Box, Text, useInput } from 'ink'; + +interface ConfirmDialogProps { + message: string; + onConfirm: () => void; + onCancel: () => void; +} + +export function ConfirmDialog({ message, onConfirm, onCancel }: ConfirmDialogProps) { + useInput((input, key) => { + if (input === 'y' || input === 'Y' || input === 'j' || input === 'J') { + onConfirm(); + } else if (input === 'n' || input === 'N' || key.escape) { + onCancel(); + } + }); + + return ( + + + Bestätigung erforderlich + + {message} + + [J] Ja [N] Nein + + + ); +} diff --git a/frontend/apps/cli/src/components/shared/ErrorDisplay.tsx b/frontend/apps/cli/src/components/shared/ErrorDisplay.tsx new file mode 100644 index 0000000..836b415 --- /dev/null +++ b/frontend/apps/cli/src/components/shared/ErrorDisplay.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Box, Text } from 'ink'; + +interface ErrorDisplayProps { + message: string; + onDismiss?: () => void; +} + +export function ErrorDisplay({ message, onDismiss }: ErrorDisplayProps) { + return ( + + + Fehler + + {message} + {onDismiss && ( + + [Enter] Schließen + + )} + + ); +} diff --git a/frontend/apps/cli/src/components/shared/FormInput.tsx b/frontend/apps/cli/src/components/shared/FormInput.tsx new file mode 100644 index 0000000..3590a84 --- /dev/null +++ b/frontend/apps/cli/src/components/shared/FormInput.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Box, Text } from 'ink'; +import TextInput from 'ink-text-input'; + +interface FormInputProps { + label: string; + value: string; + onChange: (value: string) => void; + onSubmit?: (value: string) => void; + focus: boolean; + placeholder?: string; + mask?: string; + error?: string; +} + +export function FormInput({ label, value, onChange, onSubmit, focus, placeholder, mask, error }: FormInputProps) { + return ( + + {label} + + + + + {error !== undefined && ⚠ {error}} + + ); +} diff --git a/frontend/apps/cli/src/components/shared/LoadingSpinner.tsx b/frontend/apps/cli/src/components/shared/LoadingSpinner.tsx new file mode 100644 index 0000000..897bcff --- /dev/null +++ b/frontend/apps/cli/src/components/shared/LoadingSpinner.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Text } from 'ink'; + +interface LoadingSpinnerProps { + label?: string; +} + +const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + +export function LoadingSpinner({ label = 'Laden...' }: LoadingSpinnerProps) { + const [frame, setFrame] = React.useState(0); + + React.useEffect(() => { + const timer = setInterval(() => { + setFrame((f) => (f + 1) % FRAMES.length); + }, 80); + return () => clearInterval(timer); + }, []); + + return ( + + {FRAMES[frame]} {label} + + ); +} diff --git a/frontend/apps/cli/src/components/shared/SuccessDisplay.tsx b/frontend/apps/cli/src/components/shared/SuccessDisplay.tsx new file mode 100644 index 0000000..6acdd22 --- /dev/null +++ b/frontend/apps/cli/src/components/shared/SuccessDisplay.tsx @@ -0,0 +1,26 @@ +import React, { useEffect } from 'react'; +import { Box, Text } from 'ink'; + +interface SuccessDisplayProps { + message: string; + onDismiss: () => void; +} + +export function SuccessDisplay({ message, onDismiss }: SuccessDisplayProps) { + useEffect(() => { + const timer = setTimeout(onDismiss, 3000); + return () => clearTimeout(timer); + }, [onDismiss]); + + return ( + + + Erfolg + + {message} + + [Enter] Schließen (auto-dismiss nach 3 s) + + + ); +} diff --git a/frontend/apps/cli/src/components/users/ChangePasswordScreen.tsx b/frontend/apps/cli/src/components/users/ChangePasswordScreen.tsx new file mode 100644 index 0000000..00a642f --- /dev/null +++ b/frontend/apps/cli/src/components/users/ChangePasswordScreen.tsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { client } from '../../utils/api-client.js'; +import { FormInput } from '../shared/FormInput.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { SuccessDisplay } from '../shared/SuccessDisplay.js'; +import { passwordSchema } from '@effigenix/validation'; + +type Field = 'currentPassword' | 'newPassword' | 'confirmPassword'; +const FIELDS: Field[] = ['currentPassword', 'newPassword', 'confirmPassword']; + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : 'Unbekannter Fehler'; +} + +export function ChangePasswordScreen() { + const { params, back } = useNavigation(); + const userId = params['userId'] ?? ''; + + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [activeField, setActiveField] = useState('currentPassword'); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + useInput((_input, key) => { + if (loading) return; + if (key.tab || key.downArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx + 1) % FIELDS.length] ?? f; + }); + } + if (key.upArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx - 1 + FIELDS.length) % FIELDS.length] ?? f; + }); + } + if (key.escape) { + back(); + } + }); + + const handleSubmit = async () => { + setError(null); + + if (!currentPassword.trim() || !newPassword.trim() || !confirmPassword.trim()) { + setError('Alle Felder sind Pflichtfelder.'); + return; + } + if (newPassword !== confirmPassword) { + setError('Das neue Passwort und die Bestätigung stimmen nicht überein.'); + return; + } + + const passwordResult = passwordSchema.safeParse(newPassword.trim()); + if (!passwordResult.success) { + setError(passwordResult.error.errors[0]?.message ?? 'Ungültiges Passwort'); + return; + } + + setLoading(true); + try { + await client.users.changePassword(userId, { + currentPassword: currentPassword.trim(), + newPassword: newPassword.trim(), + }); + setSuccess(true); + } catch (err) { + setError(errorMessage(err)); + } finally { + setLoading(false); + } + }; + + const handleFieldSubmit = (field: Field) => (_value: string) => { + const idx = FIELDS.indexOf(field); + if (idx < FIELDS.length - 1) { + setActiveField(FIELDS[idx + 1] ?? field); + } else { + void handleSubmit(); + } + }; + + if (loading) { + return ( + + + + ); + } + + if (success) { + return ( + + back()} /> + + ); + } + + return ( + + + Passwort ändern + + + {error && ( + setError(null)} /> + )} + + + + + + + + + + Tab/↑↓ Feld wechseln · Enter auf letztem Feld speichern · Escape Abbrechen + + + + ); +} diff --git a/frontend/apps/cli/src/components/users/UserCreateScreen.tsx b/frontend/apps/cli/src/components/users/UserCreateScreen.tsx new file mode 100644 index 0000000..33d23c5 --- /dev/null +++ b/frontend/apps/cli/src/components/users/UserCreateScreen.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { useUsers } from '../../hooks/useUsers.js'; +import { FormInput } from '../shared/FormInput.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { usernameSchema, emailSchema, passwordSchema } from '@effigenix/validation'; + +type Field = 'username' | 'email' | 'password' | 'roleName'; +const FIELDS: Field[] = ['username', 'email', 'password', 'roleName']; + +export function UserCreateScreen() { + const { navigate, back } = useNavigation(); + const { createUser, loading, error, clearError } = useUsers(); + + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [roleName, setRoleName] = useState(''); + const [activeField, setActiveField] = useState('username'); + const [validationError, setValidationError] = useState(null); + const [usernameError, setUsernameError] = useState(null); + const [emailError, setEmailError] = useState(null); + const [passwordError, setPasswordError] = useState(null); + + useInput((_input, key) => { + if (loading) return; + if (key.tab || key.downArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx + 1) % FIELDS.length] ?? f; + }); + } + if (key.upArrow) { + setActiveField((f) => { + const idx = FIELDS.indexOf(f); + return FIELDS[(idx - 1 + FIELDS.length) % FIELDS.length] ?? f; + }); + } + if (key.backspace && !key.meta) { + // Only go back if no text input is focused and the fields are empty + } + if (key.escape) { + back(); + } + }); + + const handleSubmit = async () => { + setValidationError(null); + setUsernameError(null); + setEmailError(null); + setPasswordError(null); + + const usernameResult = usernameSchema.safeParse(username.trim()); + const emailResult = emailSchema.safeParse(email.trim()); + const passwordResult = passwordSchema.safeParse(password.trim()); + + let hasError = false; + if (!usernameResult.success) { + setUsernameError(usernameResult.error.errors[0]?.message ?? 'Ungültiger Benutzername'); + hasError = true; + } + if (!emailResult.success) { + setEmailError(emailResult.error.errors[0]?.message ?? 'Ungültige E-Mail'); + hasError = true; + } + if (!passwordResult.success) { + setPasswordError(passwordResult.error.errors[0]?.message ?? 'Ungültiges Passwort'); + hasError = true; + } + if (hasError) return; + + const user = await createUser(username.trim(), email.trim(), password.trim(), roleName.trim() || undefined); + if (user) { + navigate('user-list'); + } + }; + + const handleFieldSubmit = (field: Field) => (value: string) => { + const idx = FIELDS.indexOf(field); + if (idx < FIELDS.length - 1) { + setActiveField(FIELDS[idx + 1] ?? field); + } else { + void handleSubmit(); + } + void value; + }; + + if (loading) { + return ( + + + + ); + } + + const displayError = validationError ?? error; + + return ( + + + Neuen Benutzer anlegen + + + {displayError && ( + { + setValidationError(null); + clearError(); + }} + /> + )} + + + + + + + + + + + Tab/↑↓ Feld wechseln · Enter auf letztem Feld speichern · Escape Abbrechen + + + + ); +} diff --git a/frontend/apps/cli/src/components/users/UserDetailScreen.tsx b/frontend/apps/cli/src/components/users/UserDetailScreen.tsx new file mode 100644 index 0000000..3c07b4a --- /dev/null +++ b/frontend/apps/cli/src/components/users/UserDetailScreen.tsx @@ -0,0 +1,271 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import type { RoleDTO, UserDTO } from '@effigenix/api-client'; +import { useNavigation } from '../../state/navigation-context.js'; +import { client } from '../../utils/api-client.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { SuccessDisplay } from '../shared/SuccessDisplay.js'; +import { ConfirmDialog } from '../shared/ConfirmDialog.js'; +import { RoleSelectList } from '../roles/RoleSelectList.js'; + +type MenuAction = 'toggle-lock' | 'assign-role' | 'remove-role' | 'change-password' | 'back'; +type Mode = 'menu' | 'confirm-lock' | 'assign-role' | 'remove-role'; + +interface MenuItem { + id: MenuAction; + label: (user: UserDTO) => string; +} + +const MENU_ITEMS: MenuItem[] = [ + { id: 'toggle-lock', label: (u) => (u.status === 'ACTIVE' ? '[Sperren]' : '[Entsperren]') }, + { id: 'assign-role', label: () => '[Rolle zuweisen]' }, + { id: 'remove-role', label: () => '[Rolle entfernen]' }, + { id: 'change-password', label: () => '[Passwort ändern]' }, + { id: 'back', label: () => '[Zurück]' }, +]; + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : 'Unbekannter Fehler'; +} + +export function UserDetailScreen() { + const { params, navigate, back } = useNavigation(); + const userId = params['userId'] ?? ''; + + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedAction, setSelectedAction] = useState(0); + const [mode, setMode] = useState('menu'); + const [availableRoles, setAvailableRoles] = useState([]); + const [actionLoading, setActionLoading] = useState(false); + const [successMessage, setSuccessMessage] = useState(null); + + const loadUser = async () => { + setLoading(true); + setError(null); + try { + const u = await client.users.getById(userId); + setUser(u); + } catch (err) { + setError(errorMessage(err)); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (userId) { + void loadUser(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userId]); + + useInput((_input, key) => { + if (loading || actionLoading || mode !== 'menu') return; + + if (key.upArrow) { + setSelectedAction((i) => Math.max(0, i - 1)); + } + if (key.downArrow) { + setSelectedAction((i) => Math.min(MENU_ITEMS.length - 1, i + 1)); + } + if (key.return) { + void handleAction(); + } + if (key.backspace || key.escape) { + back(); + } + }); + + const handleAction = async () => { + if (!user) return; + const item = MENU_ITEMS[selectedAction]; + if (!item) return; + + switch (item.id) { + case 'toggle-lock': { + setMode('confirm-lock'); + break; + } + case 'assign-role': { + setActionLoading(true); + try { + const allRoles = await client.roles.list(); + const userRoleIds = new Set(user.roles.map((r) => r.id)); + setAvailableRoles(allRoles.filter((r) => !userRoleIds.has(r.id))); + } catch (err) { + setError(errorMessage(err)); + } finally { + setActionLoading(false); + } + setMode('assign-role'); + break; + } + case 'remove-role': { + setAvailableRoles(user.roles); + setMode('remove-role'); + break; + } + case 'change-password': { + navigate('change-password', { userId: user.id }); + break; + } + case 'back': { + back(); + break; + } + } + }; + + const handleToggleLock = useCallback(async () => { + if (!user) return; + setMode('menu'); + setActionLoading(true); + setError(null); + try { + const updated = user.status === 'ACTIVE' + ? await client.users.lock(user.id) + : await client.users.unlock(user.id); + setUser(updated); + setSuccessMessage(user.status === 'ACTIVE' ? 'Benutzer gesperrt.' : 'Benutzer entsperrt.'); + } catch (err) { + setError(errorMessage(err)); + } finally { + setActionLoading(false); + } + }, [user]); + + const handleRoleSelect = useCallback(async (role: RoleDTO) => { + if (!user) return; + const currentMode = mode; + setMode('menu'); + setActionLoading(true); + setError(null); + try { + if (currentMode === 'assign-role') { + const updated = await client.users.assignRole(user.id, { roleName: role.name }); + setUser(updated); + setSuccessMessage(`Rolle "${role.name}" zugewiesen.`); + } else { + await client.users.removeRole(user.id, role.name); + const updated = await client.users.getById(user.id); + setUser(updated); + setSuccessMessage(`Rolle "${role.name}" entfernt.`); + } + } catch (err) { + setError(errorMessage(err)); + } finally { + setActionLoading(false); + } + }, [user, mode]); + + if (loading) { + return ; + } + + if (error && !user) { + return setError(null)} />; + } + + if (!user) { + return Benutzer nicht gefunden.; + } + + const roleNames = user.roles.map((r) => r.name).join(', ') || '–'; + const statusColor = user.status === 'ACTIVE' ? 'green' : 'red'; + + return ( + + + Benutzerdetails + + + {error && setError(null)} />} + {successMessage && ( + setSuccessMessage(null)} /> + )} + + {/* User Info */} + + + ID: + {user.id} + + + Benutzername: + {user.username} + + + E-Mail: + {user.email} + + + Status: + + {user.status} + + + + Rollen: + {roleNames} + + + Erstellt: + {new Date(user.createdAt).toLocaleString('de-DE')} + + {user.lastLogin && ( + + Letzter Login: + {new Date(user.lastLogin).toLocaleString('de-DE')} + + )} + + + {/* Confirm Lock Dialog */} + {mode === 'confirm-lock' && ( + void handleToggleLock()} + onCancel={() => setMode('menu')} + /> + )} + + {/* Role Select */} + {(mode === 'assign-role' || mode === 'remove-role') && ( + void handleRoleSelect(role)} + onCancel={() => setMode('menu')} + /> + )} + + {/* Action Menu */} + {mode === 'menu' && ( + + + Aktionen: + + {actionLoading && } + {!actionLoading && + MENU_ITEMS.map((item, index) => ( + + + {index === selectedAction ? '▶ ' : ' '} + {item.label(user)} + + + ))} + + )} + + + + ↑↓ navigieren · Enter ausführen · Backspace Zurück + + + + ); +} diff --git a/frontend/apps/cli/src/components/users/UserListScreen.tsx b/frontend/apps/cli/src/components/users/UserListScreen.tsx new file mode 100644 index 0000000..960d4f9 --- /dev/null +++ b/frontend/apps/cli/src/components/users/UserListScreen.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Text, useInput } from 'ink'; +import { useNavigation } from '../../state/navigation-context.js'; +import { useUsers } from '../../hooks/useUsers.js'; +import { LoadingSpinner } from '../shared/LoadingSpinner.js'; +import { ErrorDisplay } from '../shared/ErrorDisplay.js'; +import { UserTable } from './UserTable.js'; + +export function UserListScreen() { + const { navigate, back } = useNavigation(); + const { users, loading, error, fetchUsers, clearError } = useUsers(); + const [selectedIndex, setSelectedIndex] = useState(0); + + useEffect(() => { + void fetchUsers(); + }, [fetchUsers]); + + useInput((input, key) => { + if (loading) return; + + if (key.upArrow) { + setSelectedIndex((i) => Math.max(0, i - 1)); + } + if (key.downArrow) { + setSelectedIndex((i) => Math.min(users.length - 1, i + 1)); + } + if (key.return && users.length > 0) { + const user = users[selectedIndex]; + if (user) { + navigate('user-detail', { userId: user.id }); + } + } + if (input === 'n') { + navigate('user-create'); + } + if (key.backspace || key.escape) { + back(); + } + }); + + return ( + + + + Benutzerverwaltung + + + {' '}– {users.length} Benutzer + + + + {loading && } + + {error && !loading && ( + + )} + + {!loading && !error && ( + + )} + + + + ↑↓ navigieren · Enter Details · [n] Neu · Backspace Zurück + + + + ); +} diff --git a/frontend/apps/cli/src/components/users/UserTable.tsx b/frontend/apps/cli/src/components/users/UserTable.tsx new file mode 100644 index 0000000..e1f3793 --- /dev/null +++ b/frontend/apps/cli/src/components/users/UserTable.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Box, Text } from 'ink'; +import type { UserDTO } from '@effigenix/api-client'; + +interface UserTableProps { + users: UserDTO[]; + selectedIndex: number; +} + +export function UserTable({ users, selectedIndex }: UserTableProps) { + if (users.length === 0) { + return Keine Benutzer vorhanden.; + } + + return ( + + {/* Header */} + + + {' '} + {'#'.padEnd(3)} + {'Benutzername'.padEnd(20)} + {'E-Mail'.padEnd(30)} + {'Status'.padEnd(10)} + {'Rollen'} + + + + {'─'.repeat(80)} + + + {/* Rows */} + {users.map((user, index) => { + const isSelected = index === selectedIndex; + const statusColor = user.status === 'ACTIVE' ? 'green' : 'red'; + const rowColor = isSelected ? 'cyan' : 'white'; + const prefix = isSelected ? '▶ ' : ' '; + const roleNames = user.roles.map((r) => r.name).join(', ') || '–'; + + return ( + + + {prefix} + {String(index + 1).padEnd(3)} + {user.username.padEnd(20)} + {user.email.padEnd(30)} + + {user.status.padEnd(10)} + {roleNames} + + ); + })} + + ); +} diff --git a/frontend/apps/cli/src/hooks/useRoles.ts b/frontend/apps/cli/src/hooks/useRoles.ts new file mode 100644 index 0000000..3b48f17 --- /dev/null +++ b/frontend/apps/cli/src/hooks/useRoles.ts @@ -0,0 +1,37 @@ +import { useState, useCallback } from 'react'; +import type { RoleDTO } from '@effigenix/api-client'; +import { client } from '../utils/api-client.js'; + +interface RolesState { + roles: RoleDTO[]; + loading: boolean; + error: string | null; +} + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : 'Unbekannter Fehler'; +} + +export function useRoles() { + const [state, setState] = useState({ + roles: [], + loading: false, + error: null, + }); + + const fetchRoles = useCallback(async () => { + setState((s) => ({ ...s, loading: true, error: null })); + try { + const roles = await client.roles.list(); + setState({ roles, loading: false, error: null }); + } catch (err) { + setState((s) => ({ ...s, loading: false, error: errorMessage(err) })); + } + }, []); + + const clearError = useCallback(() => { + setState((s) => ({ ...s, error: null })); + }, []); + + return { ...state, fetchRoles, clearError }; +} diff --git a/frontend/apps/cli/src/hooks/useTerminalSize.ts b/frontend/apps/cli/src/hooks/useTerminalSize.ts new file mode 100644 index 0000000..4259974 --- /dev/null +++ b/frontend/apps/cli/src/hooks/useTerminalSize.ts @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react'; +import { useStdout } from 'ink'; + +interface TerminalSize { + columns: number; + rows: number; +} + +export function useTerminalSize(): TerminalSize { + const { stdout } = useStdout(); + const [size, setSize] = useState({ + columns: stdout?.columns ?? 80, + rows: stdout?.rows ?? 24, + }); + + useEffect(() => { + if (!stdout) return; + const handleResize = () => { + setSize({ columns: stdout.columns, rows: stdout.rows }); + }; + stdout.on('resize', handleResize); + return () => { + stdout.off('resize', handleResize); + }; + }, [stdout]); + + return size; +} diff --git a/frontend/apps/cli/src/hooks/useUsers.ts b/frontend/apps/cli/src/hooks/useUsers.ts new file mode 100644 index 0000000..3abc3d9 --- /dev/null +++ b/frontend/apps/cli/src/hooks/useUsers.ts @@ -0,0 +1,137 @@ +import { useState, useCallback } from 'react'; +import type { UserDTO } from '@effigenix/api-client'; +import { client } from '../utils/api-client.js'; + +interface UsersState { + users: UserDTO[]; + loading: boolean; + error: string | null; +} + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : 'Unbekannter Fehler'; +} + +export function useUsers() { + const [state, setState] = useState({ + users: [], + loading: false, + error: null, + }); + + const fetchUsers = useCallback(async () => { + setState((s) => ({ ...s, loading: true, error: null })); + try { + const users = await client.users.list(); + setState({ users, loading: false, error: null }); + } catch (err) { + setState((s) => ({ ...s, loading: false, error: errorMessage(err) })); + } + }, []); + + const createUser = useCallback( + async (username: string, email: string, password: string, roleName?: string) => { + setState((s) => ({ ...s, loading: true, error: null })); + try { + const user = await client.users.create({ + username, + email, + password, + roleNames: roleName ? [roleName] : [], + }); + setState((s) => ({ users: [...s.users, user], loading: false, error: null })); + return user; + } catch (err) { + setState((s) => ({ ...s, loading: false, error: errorMessage(err) })); + return null; + } + }, + [], + ); + + const lockUser = useCallback(async (id: string) => { + try { + const user = await client.users.lock(id); + setState((s) => ({ + ...s, + users: s.users.map((u) => (u.id === id ? user : u)), + })); + return user; + } catch (err) { + setState((s) => ({ ...s, error: errorMessage(err) })); + return null; + } + }, []); + + const unlockUser = useCallback(async (id: string) => { + try { + const user = await client.users.unlock(id); + setState((s) => ({ + ...s, + users: s.users.map((u) => (u.id === id ? user : u)), + })); + return user; + } catch (err) { + setState((s) => ({ ...s, error: errorMessage(err) })); + return null; + } + }, []); + + const assignRole = useCallback(async (id: string, roleName: string) => { + try { + const user = await client.users.assignRole(id, { roleName }); + setState((s) => ({ + ...s, + users: s.users.map((u) => (u.id === id ? user : u)), + })); + return user; + } catch (err) { + setState((s) => ({ ...s, error: errorMessage(err) })); + return null; + } + }, []); + + const removeRole = useCallback(async (id: string, roleName: string) => { + try { + await client.users.removeRole(id, roleName); + const updated = await client.users.getById(id); + setState((s) => ({ + ...s, + users: s.users.map((u) => (u.id === id ? updated : u)), + })); + return true; + } catch (err) { + setState((s) => ({ ...s, error: errorMessage(err) })); + return false; + } + }, []); + + const changePassword = useCallback( + async (id: string, currentPassword: string, newPassword: string) => { + try { + await client.users.changePassword(id, { currentPassword, newPassword }); + return true; + } catch (err) { + setState((s) => ({ ...s, error: errorMessage(err) })); + return false; + } + }, + [], + ); + + const clearError = useCallback(() => { + setState((s) => ({ ...s, error: null })); + }, []); + + return { + ...state, + fetchUsers, + createUser, + lockUser, + unlockUser, + assignRole, + removeRole, + changePassword, + clearError, + }; +} diff --git a/frontend/apps/cli/src/index.tsx b/frontend/apps/cli/src/index.tsx new file mode 100644 index 0000000..622eb6c --- /dev/null +++ b/frontend/apps/cli/src/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render } from 'ink'; +import { App } from './App.js'; + +// Alternate screen buffer + clear + hide cursor +process.stdout.write('\x1b[?1049h\x1b[2J\x1b[H\x1b[?25l'); + +const cleanup = () => { + process.stdout.write('\x1b[?25h\x1b[?1049l'); +}; + +process.on('exit', cleanup); +process.on('SIGINT', () => { + cleanup(); + process.exit(0); +}); +process.on('SIGTERM', () => { + cleanup(); + process.exit(0); +}); + +const { waitUntilExit } = render(); + +waitUntilExit().then(cleanup).catch(cleanup); diff --git a/frontend/apps/cli/src/state/auth-context.tsx b/frontend/apps/cli/src/state/auth-context.tsx new file mode 100644 index 0000000..f3106da --- /dev/null +++ b/frontend/apps/cli/src/state/auth-context.tsx @@ -0,0 +1,148 @@ +import React, { createContext, useCallback, useContext, useEffect, useReducer } from 'react'; +import { RefreshTokenExpiredError } from '@effigenix/api-client'; +import { tokenStorage } from '../utils/token-storage.js'; +import { client } from '../utils/api-client.js'; + +export interface AuthUser { + username: string; +} + +interface AuthState { + isAuthenticated: boolean; + user: AuthUser | null; + loading: boolean; + error: string | null; +} + +type AuthAction = + | { type: 'SET_LOADING'; loading: boolean } + | { type: 'SET_USER'; user: AuthUser } + | { type: 'SET_ERROR'; error: string } + | { type: 'CLEAR_ERROR' } + | { type: 'LOGOUT' }; + +function authReducer(state: AuthState, action: AuthAction): AuthState { + switch (action.type) { + case 'SET_LOADING': + return { ...state, loading: action.loading }; + case 'SET_USER': + return { ...state, isAuthenticated: true, user: action.user, loading: false, error: null }; + case 'SET_ERROR': + return { ...state, loading: false, error: action.error }; + case 'CLEAR_ERROR': + return { ...state, error: null }; + case 'LOGOUT': + return { isAuthenticated: false, user: null, loading: false, error: null }; + } +} + +interface AuthContextValue { + isAuthenticated: boolean; + user: AuthUser | null; + loading: boolean; + error: string | null; + login: (username: string, password: string) => Promise; + logout: () => Promise; + clearError: () => void; +} + +const AuthContext = createContext(null); + +interface AuthProviderProps { + children: React.ReactNode; + onAuthRequired?: () => void; + onLogout?: () => void; +} + +export function AuthProvider({ children, onLogout }: AuthProviderProps) { + const [state, dispatch] = useReducer(authReducer, { + isAuthenticated: false, + user: null, + loading: true, + error: null, + }); + + // Beim Start: prüfen ob bereits gültige Session vorhanden + useEffect(() => { + void (async () => { + try { + const token = await tokenStorage.getAccessToken(); + const refreshToken = await tokenStorage.getRefreshToken(); + if (token && refreshToken) { + // Token existiert und ist noch gültig + dispatch({ type: 'SET_USER', user: { username: 'Eingeloggt' } }); + } else if (refreshToken) { + // Access Token abgelaufen, aber Refresh Token vorhanden → Refresh versuchen + try { + const refreshed = await client.auth.refresh({ refreshToken }); + await tokenStorage.saveTokens( + refreshed.accessToken, + refreshed.refreshToken, + refreshed.expiresAt, + ); + dispatch({ type: 'SET_USER', user: { username: 'Eingeloggt' } }); + } catch { + await tokenStorage.clearTokens(); + dispatch({ type: 'LOGOUT' }); + } + } else { + dispatch({ type: 'SET_LOADING', loading: false }); + } + } catch { + dispatch({ type: 'SET_LOADING', loading: false }); + } + })(); + }, []); + + const login = useCallback(async (username: string, password: string): Promise => { + dispatch({ type: 'SET_LOADING', loading: true }); + dispatch({ type: 'CLEAR_ERROR' }); + try { + const response = await client.auth.login({ username, password }); + await tokenStorage.saveTokens(response.accessToken, response.refreshToken, response.expiresAt); + dispatch({ type: 'SET_USER', user: { username } }); + return true; + } catch (err) { + if (err instanceof RefreshTokenExpiredError) { + dispatch({ type: 'SET_ERROR', error: 'Session abgelaufen. Bitte neu anmelden.' }); + } else if (err instanceof Error) { + const message = err.message.includes('401') + ? 'Ungültiger Benutzername oder Passwort.' + : err.message; + dispatch({ type: 'SET_ERROR', error: message }); + } else { + dispatch({ type: 'SET_ERROR', error: 'Anmeldung fehlgeschlagen.' }); + } + return false; + } + }, []); + + const logout = useCallback(async (): Promise => { + try { + await client.auth.logout(); + } catch { + // Logout-Request kann fehlschlagen wenn Token abgelaufen – trotzdem lokale Session löschen + } + await tokenStorage.clearTokens(); + dispatch({ type: 'LOGOUT' }); + onLogout?.(); + }, [onLogout]); + + const clearError = useCallback(() => dispatch({ type: 'CLEAR_ERROR' }), []); + + const value: AuthContextValue = { + ...state, + login, + logout, + clearError, + }; + + return {children}; +} + +export function useAuth(): AuthContextValue { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error('useAuth must be used within AuthProvider'); + return ctx; +} + diff --git a/frontend/apps/cli/src/state/navigation-context.tsx b/frontend/apps/cli/src/state/navigation-context.tsx new file mode 100644 index 0000000..245d4bd --- /dev/null +++ b/frontend/apps/cli/src/state/navigation-context.tsx @@ -0,0 +1,77 @@ +import React, { createContext, useContext, useReducer } from 'react'; + +export type Screen = + | 'login' + | 'main-menu' + | 'user-list' + | 'user-create' + | 'user-detail' + | 'change-password' + | 'role-list' + | 'role-detail'; + +interface NavigationState { + current: Screen; + history: Screen[]; + params: Record; +} + +type NavigationAction = + | { type: 'NAVIGATE'; screen: Screen; params?: Record } + | { type: 'BACK' }; + +function navigationReducer(state: NavigationState, action: NavigationAction): NavigationState { + switch (action.type) { + case 'NAVIGATE': + return { + current: action.screen, + history: [...state.history, state.current], + params: action.params ?? {}, + }; + case 'BACK': { + const history = [...state.history]; + const previous = history.pop(); + if (!previous) return state; + return { current: previous, history, params: {} }; + } + } +} + +interface NavigationContextValue { + current: Screen; + params: Record; + canGoBack: boolean; + navigate: (screen: Screen, params?: Record) => void; + back: () => void; +} + +const NavigationContext = createContext(null); + +interface NavigationProviderProps { + children: React.ReactNode; + initialScreen?: Screen; +} + +export function NavigationProvider({ children, initialScreen = 'login' }: NavigationProviderProps) { + const [state, dispatch] = useReducer(navigationReducer, { + current: initialScreen, + history: [], + params: {}, + }); + + const value: NavigationContextValue = { + current: state.current, + params: state.params, + canGoBack: state.history.length > 0, + navigate: (screen, params) => dispatch({ type: 'NAVIGATE', screen, params: params ?? {} }), + back: () => dispatch({ type: 'BACK' }), + }; + + return {children}; +} + +export function useNavigation(): NavigationContextValue { + const ctx = useContext(NavigationContext); + if (!ctx) throw new Error('useNavigation must be used within NavigationProvider'); + return ctx; +} diff --git a/frontend/apps/cli/src/utils/api-client.ts b/frontend/apps/cli/src/utils/api-client.ts new file mode 100644 index 0000000..a29f730 --- /dev/null +++ b/frontend/apps/cli/src/utils/api-client.ts @@ -0,0 +1,5 @@ +import { createEffigenixClient } from '@effigenix/api-client'; +import type { EffigenixClient } from '@effigenix/api-client'; +import { tokenStorage } from './token-storage.js'; + +export const client: EffigenixClient = createEffigenixClient(tokenStorage); diff --git a/frontend/apps/cli/src/utils/token-storage.ts b/frontend/apps/cli/src/utils/token-storage.ts new file mode 100644 index 0000000..bd34de8 --- /dev/null +++ b/frontend/apps/cli/src/utils/token-storage.ts @@ -0,0 +1,82 @@ +/** + * Filesystem-based TokenProvider. + * Stores tokens in ~/.effigenix/config.json with file permissions 600. + */ + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import os from 'node:os'; +import type { TokenProvider } from '@effigenix/api-client'; + +interface StoredAuth { + accessToken: string; + refreshToken: string; + expiresAt: string; +} + +interface StoredConfig { + apiBaseUrl?: string; + auth?: StoredAuth; +} + +const CONFIG_DIR = path.join(os.homedir(), '.effigenix'); +const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); + +async function loadConfig(): Promise { + try { + const raw = await fs.readFile(CONFIG_FILE, 'utf-8'); + return JSON.parse(raw) as StoredConfig; + } catch { + return {}; + } +} + +async function saveConfig(config: StoredConfig): Promise { + await fs.mkdir(CONFIG_DIR, { recursive: true }); + await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 }); +} + +export const tokenStorage: TokenProvider = { + async getAccessToken(): Promise { + const config = await loadConfig(); + if (!config.auth) return null; + + // Proaktiver Refresh: wenn weniger als 5 Minuten bis Ablauf + const expiresAt = new Date(config.auth.expiresAt).getTime(); + const timeUntilExpiry = expiresAt - Date.now(); + if (timeUntilExpiry < 5 * 60 * 1000) { + // Token bald abgelaufen – Aufrufer (RefreshInterceptor) übernimmt Refresh + return null; + } + + return config.auth.accessToken; + }, + + async getRefreshToken(): Promise { + const config = await loadConfig(); + return config.auth?.refreshToken ?? null; + }, + + async saveTokens(accessToken: string, refreshToken: string, expiresAt: string): Promise { + const config = await loadConfig(); + config.auth = { accessToken, refreshToken, expiresAt }; + await saveConfig(config); + }, + + async clearTokens(): Promise { + const config = await loadConfig(); + delete config.auth; + await saveConfig(config); + }, +}; + +export async function getStoredApiBaseUrl(): Promise { + const config = await loadConfig(); + return config.apiBaseUrl; +} + +export async function saveApiBaseUrl(url: string): Promise { + const config = await loadConfig(); + config.apiBaseUrl = url; + await saveConfig(config); +} diff --git a/frontend/apps/cli/tsconfig.json b/frontend/apps/cli/tsconfig.json new file mode 100644 index 0000000..e504366 --- /dev/null +++ b/frontend/apps/cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": false, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/apps/cli/vitest.config.ts b/frontend/apps/cli/vitest.config.ts new file mode 100644 index 0000000..4ac6027 --- /dev/null +++ b/frontend/apps/cli/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + }, +}); diff --git a/frontend/packages/api-client/package.json b/frontend/packages/api-client/package.json new file mode 100644 index 0000000..dac2988 --- /dev/null +++ b/frontend/packages/api-client/package.json @@ -0,0 +1,42 @@ +{ + "name": "@effigenix/api-client", + "version": "0.1.0", + "description": "HTTP client for the Effigenix ERP API", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist .turbo", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@effigenix/config": "workspace:*", + "@effigenix/types": "workspace:*", + "axios": "^1.6.7" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsup": "^8.0.1", + "typescript": "^5.3.3", + "vitest": "^1.2.2", + "axios-mock-adapter": "^1.22.0" + }, + "tsup": { + "entry": ["src/index.ts"], + "format": ["esm"], + "dts": true, + "clean": true, + "sourcemap": true, + "splitting": false + } +} diff --git a/frontend/packages/api-client/src/__tests__/errors.test.ts b/frontend/packages/api-client/src/__tests__/errors.test.ts new file mode 100644 index 0000000..a029ea3 --- /dev/null +++ b/frontend/packages/api-client/src/__tests__/errors.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest'; +import { + ApiError, + AuthenticationError, + NetworkError, + RefreshTokenExpiredError, +} from '../errors.js'; + +describe('ApiError', () => { + it('stores status code and message', () => { + const error = new ApiError('Not found', 404); + expect(error.message).toBe('Not found'); + expect(error.status).toBe(404); + expect(error.name).toBe('ApiError'); + }); + + it('stores optional validation errors', () => { + const validationErrors = [{ field: 'email', message: 'Invalid email' }]; + const error = new ApiError('Validation failed', 400, undefined, validationErrors); + expect(error.validationErrors).toEqual(validationErrors); + }); + + it('stores optional code', () => { + const error = new ApiError('Conflict', 409, 'USERNAME_TAKEN'); + expect(error.code).toBe('USERNAME_TAKEN'); + }); +}); + +describe('AuthenticationError', () => { + it('defaults to 401 status', () => { + const error = new AuthenticationError(); + expect(error.status).toBe(401); + expect(error.name).toBe('AuthenticationError'); + }); + + it('accepts custom status for 403', () => { + const error = new AuthenticationError('Forbidden', 403); + expect(error.status).toBe(403); + }); +}); + +describe('NetworkError', () => { + it('defaults to non-timeout', () => { + const error = new NetworkError(); + expect(error.isTimeout).toBe(false); + expect(error.name).toBe('NetworkError'); + }); + + it('flags timeout errors', () => { + const error = new NetworkError('Request timed out', true); + expect(error.isTimeout).toBe(true); + }); +}); + +describe('RefreshTokenExpiredError', () => { + it('is an AuthenticationError with correct message', () => { + const error = new RefreshTokenExpiredError(); + expect(error).toBeInstanceOf(AuthenticationError); + expect(error.message).toBe('Session expired. Please log in again.'); + expect(error.name).toBe('RefreshTokenExpiredError'); + }); +}); diff --git a/frontend/packages/api-client/src/__tests__/interceptors.test.ts b/frontend/packages/api-client/src/__tests__/interceptors.test.ts new file mode 100644 index 0000000..c55b4ce --- /dev/null +++ b/frontend/packages/api-client/src/__tests__/interceptors.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { createApiClient } from '../client.js'; +import { ApiError, AuthenticationError, NetworkError, RefreshTokenExpiredError } from '../errors.js'; +import type { TokenProvider } from '../token-provider.js'; + +function makeTokenProvider(overrides: Partial = {}): TokenProvider { + return { + getAccessToken: vi.fn().mockResolvedValue('access-token'), + getRefreshToken: vi.fn().mockResolvedValue('refresh-token'), + saveTokens: vi.fn().mockResolvedValue(undefined), + clearTokens: vi.fn().mockResolvedValue(undefined), + ...overrides, + }; +} + +describe('Auth interceptor', () => { + it('attaches Authorization header to requests', async () => { + const tokenProvider = makeTokenProvider(); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + mock.onGet('/test').reply(200, { ok: true }); + + await client.get('/test'); + + expect(mock.history['get']?.[0]?.headers?.['Authorization']).toBe('Bearer access-token'); + }); + + it('does not attach header when no token is stored', async () => { + const tokenProvider = makeTokenProvider({ + getAccessToken: vi.fn().mockResolvedValue(null), + }); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + mock.onGet('/test').reply(200, {}); + + await client.get('/test'); + + expect(mock.history['get']?.[0]?.headers?.['Authorization']).toBeUndefined(); + }); +}); + +describe('Error interceptor', () => { + it('converts 401 to AuthenticationError', async () => { + const client = createApiClient({}, makeTokenProvider({ + getRefreshToken: vi.fn().mockResolvedValue(null), + })); + const mock = new MockAdapter(client); + + mock.onGet('/secure').reply(401, { message: 'Unauthorized' }); + + await expect(client.get('/secure')).rejects.toBeInstanceOf(AuthenticationError); + }); + + it('converts 404 to ApiError with correct status', async () => { + const client = createApiClient({}, makeTokenProvider()); + const mock = new MockAdapter(client); + + mock.onGet('/missing').reply(404, { message: 'Not found' }); + + await expect(client.get('/missing')).rejects.toMatchObject({ + status: 404, + message: 'Not found', + }); + }); + + it('converts network timeout to NetworkError with isTimeout=true', async () => { + const client = createApiClient({ timeoutMs: 100 }, makeTokenProvider()); + const mock = new MockAdapter(client); + + mock.onGet('/slow').timeout(); + + const error = await client.get('/slow').catch((e: unknown) => e); + expect(error).toBeInstanceOf(NetworkError); + expect((error as NetworkError).isTimeout).toBe(true); + }); + + it('converts network error to NetworkError', async () => { + const client = createApiClient({}, makeTokenProvider()); + const mock = new MockAdapter(client); + + mock.onGet('/down').networkError(); + + await expect(client.get('/down')).rejects.toBeInstanceOf(NetworkError); + }); +}); + +describe('Refresh interceptor', () => { + it('retries request with new token after successful refresh', async () => { + const tokenProvider = makeTokenProvider(); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + // First call returns 401, then after refresh returns 200 + let callCount = 0; + mock.onGet('/api/data').reply(() => { + callCount++; + return callCount === 1 ? [401, { message: 'Unauthorized' }] : [200, { data: 'ok' }]; + }); + + mock.onPost('/api/auth/refresh').reply(200, { + accessToken: 'new-access-token', + refreshToken: 'new-refresh-token', + expiresAt: '2026-02-17T15:00:00Z', + }); + + const response = await client.get('/api/data'); + expect(response.data).toEqual({ data: 'ok' }); + expect(tokenProvider.saveTokens).toHaveBeenCalledWith( + 'new-access-token', + 'new-refresh-token', + '2026-02-17T15:00:00Z', + ); + }); + + it('throws RefreshTokenExpiredError when no refresh token is stored', async () => { + const tokenProvider = makeTokenProvider({ + getRefreshToken: vi.fn().mockResolvedValue(null), + }); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + mock.onGet('/api/data').reply(401, { message: 'Unauthorized' }); + + await expect(client.get('/api/data')).rejects.toBeInstanceOf(RefreshTokenExpiredError); + expect(tokenProvider.clearTokens).toHaveBeenCalled(); + }); + + it('throws RefreshTokenExpiredError when refresh request fails', async () => { + const tokenProvider = makeTokenProvider(); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + mock.onGet('/api/data').reply(401, { message: 'Unauthorized' }); + mock.onPost('/api/auth/refresh').reply(401, { message: 'Refresh token expired' }); + + await expect(client.get('/api/data')).rejects.toBeInstanceOf(RefreshTokenExpiredError); + expect(tokenProvider.clearTokens).toHaveBeenCalled(); + }); + + it('does not retry login endpoint on 401', async () => { + const tokenProvider = makeTokenProvider(); + const client = createApiClient({}, tokenProvider); + const mock = new MockAdapter(client); + + mock.onPost('/api/auth/login').reply(401, { message: 'Invalid credentials' }); + + await expect(client.post('/api/auth/login', {})).rejects.toBeInstanceOf(AuthenticationError); + expect(tokenProvider.saveTokens).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/packages/api-client/src/client.ts b/frontend/packages/api-client/src/client.ts new file mode 100644 index 0000000..fcfc2fd --- /dev/null +++ b/frontend/packages/api-client/src/client.ts @@ -0,0 +1,40 @@ +/** + * Base axios client for the Effigenix API + */ + +import axios, { type AxiosInstance } from 'axios'; +import { type ApiConfig, DEFAULT_API_CONFIG } from '@effigenix/config'; +import { setupAuthInterceptor } from './interceptors/auth-interceptor.js'; +import { setupRefreshInterceptor } from './interceptors/refresh-interceptor.js'; +import { setupErrorInterceptor } from './interceptors/error-interceptor.js'; +import type { TokenProvider } from './token-provider.js'; + +export type { AxiosInstance }; + +/** + * Creates and configures an axios instance with all interceptors. + * + * @param config - Optional API configuration (defaults to localhost:8080) + * @param tokenProvider - Provides access/refresh tokens and handles token storage + */ +export function createApiClient( + config: Partial = {}, + tokenProvider: TokenProvider, +): AxiosInstance { + const resolvedConfig: ApiConfig = { ...DEFAULT_API_CONFIG, ...config }; + + const client = axios.create({ + baseURL: resolvedConfig.baseUrl, + timeout: resolvedConfig.timeoutMs, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }); + + setupAuthInterceptor(client, tokenProvider); + setupRefreshInterceptor(client, tokenProvider); + setupErrorInterceptor(client); + + return client; +} diff --git a/frontend/packages/api-client/src/errors.ts b/frontend/packages/api-client/src/errors.ts new file mode 100644 index 0000000..3b28b46 --- /dev/null +++ b/frontend/packages/api-client/src/errors.ts @@ -0,0 +1,66 @@ +/** + * Custom error classes for the Effigenix API client + */ + +export interface ValidationErrorDetail { + field: string; + message: string; + code?: string; +} + +/** + * Base error for all API errors. + * Wraps HTTP error responses from the backend. + */ +export class ApiError extends Error { + readonly status: number; + readonly code?: string; + readonly validationErrors?: ValidationErrorDetail[]; + + constructor( + message: string, + status: number, + code?: string, + validationErrors?: ValidationErrorDetail[], + ) { + super(message); + this.name = 'ApiError'; + this.status = status; + if (code !== undefined) this.code = code; + if (validationErrors !== undefined) this.validationErrors = validationErrors; + } +} + +/** + * Thrown when the server returns 401 (Unauthorized) or 403 (Forbidden). + */ +export class AuthenticationError extends ApiError { + constructor(message: string = 'Authentication required', status: number = 401) { + super(message, status); + this.name = 'AuthenticationError'; + } +} + +/** + * Thrown when there is no network connection or the request times out. + */ +export class NetworkError extends Error { + readonly isTimeout: boolean; + + constructor(message: string = 'Network error', isTimeout: boolean = false) { + super(message); + this.name = 'NetworkError'; + this.isTimeout = isTimeout; + } +} + +/** + * Thrown when the refresh token has expired or is invalid. + * The user must re-authenticate. + */ +export class RefreshTokenExpiredError extends AuthenticationError { + constructor() { + super('Session expired. Please log in again.', 401); + this.name = 'RefreshTokenExpiredError'; + } +} diff --git a/frontend/packages/api-client/src/index.ts b/frontend/packages/api-client/src/index.ts new file mode 100644 index 0000000..04238b6 --- /dev/null +++ b/frontend/packages/api-client/src/index.ts @@ -0,0 +1,69 @@ +/** + * @effigenix/api-client + * + * Type-safe HTTP client for the Effigenix ERP API. + * + * Usage: + * + * ```ts + * import { createEffigenixClient } from '@effigenix/api-client'; + * + * const client = createEffigenixClient(tokenProvider); + * const users = await client.users.list(); + * ``` + */ + +export { createApiClient } from './client.js'; +export type { TokenProvider } from './token-provider.js'; +export { createAuthResource } from './resources/auth.js'; +export { createUsersResource } from './resources/users.js'; +export { createRolesResource } from './resources/roles.js'; +export { + ApiError, + AuthenticationError, + NetworkError, + RefreshTokenExpiredError, +} from './errors.js'; +export type { ValidationErrorDetail } from './errors.js'; +export type { + LoginRequest, + LoginResponse, + RefreshTokenRequest, + AuthResource, +} from './resources/auth.js'; +export type { + UserDTO, + RoleDTO, + CreateUserRequest, + UpdateUserRequest, + ChangePasswordRequest, + AssignRoleRequest, + UsersResource, +} from './resources/users.js'; +export type { RolesResource } from './resources/roles.js'; + +import { createApiClient } from './client.js'; +import { createAuthResource } from './resources/auth.js'; +import { createUsersResource } from './resources/users.js'; +import { createRolesResource } from './resources/roles.js'; +import type { TokenProvider } from './token-provider.js'; +import type { ApiConfig } from '@effigenix/config'; + +/** + * Convenience factory that creates a fully-configured Effigenix API client + * with all resource modules attached. + */ +export function createEffigenixClient( + tokenProvider: TokenProvider, + config: Partial = {}, +) { + const axiosClient = createApiClient(config, tokenProvider); + + return { + auth: createAuthResource(axiosClient), + users: createUsersResource(axiosClient), + roles: createRolesResource(axiosClient), + }; +} + +export type EffigenixClient = ReturnType; diff --git a/frontend/packages/api-client/src/interceptors/auth-interceptor.ts b/frontend/packages/api-client/src/interceptors/auth-interceptor.ts new file mode 100644 index 0000000..f5dd6e6 --- /dev/null +++ b/frontend/packages/api-client/src/interceptors/auth-interceptor.ts @@ -0,0 +1,16 @@ +/** + * Auth interceptor: attaches the JWT access token to every outgoing request. + */ + +import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'; +import type { TokenProvider } from '../token-provider.js'; + +export function setupAuthInterceptor(client: AxiosInstance, tokenProvider: TokenProvider): void { + client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => { + const token = await tokenProvider.getAccessToken(); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }); +} diff --git a/frontend/packages/api-client/src/interceptors/error-interceptor.ts b/frontend/packages/api-client/src/interceptors/error-interceptor.ts new file mode 100644 index 0000000..57a5680 --- /dev/null +++ b/frontend/packages/api-client/src/interceptors/error-interceptor.ts @@ -0,0 +1,48 @@ +/** + * Error interceptor: converts backend ErrorResponse objects and network errors + * into typed ApiError / NetworkError instances. + */ + +import type { AxiosInstance, AxiosError } from 'axios'; +import { ApiError, AuthenticationError, NetworkError } from '../errors.js'; + +interface BackendErrorResponse { + message?: string; + error?: string; + status?: number; + errors?: Array<{ field: string; message: string; code?: string }>; +} + +export function setupErrorInterceptor(client: AxiosInstance): void { + client.interceptors.response.use( + undefined, + (error: unknown) => { + // Pass through errors that aren't axios errors (e.g. RefreshTokenExpiredError) + const axiosError = error as AxiosError; + if (!axiosError.isAxiosError) { + return Promise.reject(error); + } + + // Network / timeout error (no response received) + if (!axiosError.response) { + const isTimeout = axiosError.code === 'ECONNABORTED'; + return Promise.reject( + new NetworkError( + isTimeout ? 'Request timed out' : 'Network error – is the backend running?', + isTimeout, + ), + ); + } + + const { status, data } = axiosError.response; + const message = data?.message ?? data?.error ?? axiosError.message ?? 'Unknown error'; + const validationErrors = data?.errors; + + if (status === 401 || status === 403) { + return Promise.reject(new AuthenticationError(message, status)); + } + + return Promise.reject(new ApiError(message, status, undefined, validationErrors)); + }, + ); +} diff --git a/frontend/packages/api-client/src/interceptors/refresh-interceptor.ts b/frontend/packages/api-client/src/interceptors/refresh-interceptor.ts new file mode 100644 index 0000000..66526ef --- /dev/null +++ b/frontend/packages/api-client/src/interceptors/refresh-interceptor.ts @@ -0,0 +1,69 @@ +/** + * Refresh interceptor: on 401 errors, attempts a token refresh and retries the + * original request. If the refresh also fails, clears tokens and re-throws. + */ + +import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios'; +import { API_PATHS } from '@effigenix/config'; +import type { TokenProvider } from '../token-provider.js'; +import { RefreshTokenExpiredError } from '../errors.js'; + +/** Marker on request config to prevent infinite refresh loops */ +interface RetryConfig extends InternalAxiosRequestConfig { + _retried?: boolean; +} + +interface LoginResponse { + accessToken: string; + refreshToken: string; + expiresAt: string; +} + +export function setupRefreshInterceptor(client: AxiosInstance, tokenProvider: TokenProvider): void { + client.interceptors.response.use( + undefined, + async (error: unknown) => { + // Only process AxiosErrors with a response + const axiosError = error as AxiosError; + if (!axiosError.isAxiosError) { + return Promise.reject(error); + } + + const config = axiosError.config as RetryConfig | undefined; + + // Only handle 401 errors that haven't been retried yet and have a config + if (axiosError.response?.status !== 401 || config?._retried || !config) { + return Promise.reject(error); + } + + // Skip refresh for auth endpoints themselves (login/refresh) + const url = config.url ?? ''; + if (url.includes(API_PATHS.auth.login) || url.includes(API_PATHS.auth.refresh)) { + return Promise.reject(error); + } + + config._retried = true; + + const refreshToken = await tokenProvider.getRefreshToken(); + if (!refreshToken) { + await tokenProvider.clearTokens(); + return Promise.reject(new RefreshTokenExpiredError()); + } + + try { + // Use the same client so MockAdapter (tests) and auth interceptor work, + // but mark _retried on a separate config so we don't loop. + const response = await client.post(API_PATHS.auth.refresh, { refreshToken }); + const { accessToken, refreshToken: newRefreshToken, expiresAt } = response.data; + await tokenProvider.saveTokens(accessToken, newRefreshToken, expiresAt); + + // Retry the original request with the new access token + config.headers.Authorization = `Bearer ${accessToken}`; + return await client.request(config); + } catch { + await tokenProvider.clearTokens(); + return Promise.reject(new RefreshTokenExpiredError()); + } + }, + ); +} diff --git a/frontend/packages/api-client/src/resources/auth.ts b/frontend/packages/api-client/src/resources/auth.ts new file mode 100644 index 0000000..9718f56 --- /dev/null +++ b/frontend/packages/api-client/src/resources/auth.ts @@ -0,0 +1,43 @@ +/** + * Auth resource: login, logout, refresh + */ + +import type { AxiosInstance } from 'axios'; +import { API_PATHS } from '@effigenix/config'; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface LoginResponse { + accessToken: string; + tokenType: string; + expiresIn: number; + expiresAt: string; + refreshToken: string; +} + +export interface RefreshTokenRequest { + refreshToken: string; +} + +export function createAuthResource(client: AxiosInstance) { + return { + async login(request: LoginRequest): Promise { + const response = await client.post(API_PATHS.auth.login, request); + return response.data; + }, + + async logout(): Promise { + await client.post(API_PATHS.auth.logout); + }, + + async refresh(request: RefreshTokenRequest): Promise { + const response = await client.post(API_PATHS.auth.refresh, request); + return response.data; + }, + }; +} + +export type AuthResource = ReturnType; diff --git a/frontend/packages/api-client/src/resources/roles.ts b/frontend/packages/api-client/src/resources/roles.ts new file mode 100644 index 0000000..99045a9 --- /dev/null +++ b/frontend/packages/api-client/src/resources/roles.ts @@ -0,0 +1,23 @@ +/** + * Roles resource: list all roles + */ + +import type { AxiosInstance } from 'axios'; +import { API_PATHS } from '@effigenix/config'; + +export interface RoleDTO { + id: string; + name: string; + permissions: string[]; +} + +export function createRolesResource(client: AxiosInstance) { + return { + async list(): Promise { + const response = await client.get(API_PATHS.roles.base); + return response.data; + }, + }; +} + +export type RolesResource = ReturnType; diff --git a/frontend/packages/api-client/src/resources/users.ts b/frontend/packages/api-client/src/resources/users.ts new file mode 100644 index 0000000..17489bf --- /dev/null +++ b/frontend/packages/api-client/src/resources/users.ts @@ -0,0 +1,94 @@ +/** + * Users resource: CRUD, lock/unlock, roles, password + */ + +import type { AxiosInstance } from 'axios'; +import { API_PATHS } from '@effigenix/config'; + +export interface UserDTO { + id: string; + username: string; + email: string; + roles: RoleDTO[]; + branchId?: string; + status: 'ACTIVE' | 'LOCKED'; + createdAt: string; + lastLogin?: string; +} + +export interface RoleDTO { + id: string; + name: string; + permissions: string[]; +} + +export interface CreateUserRequest { + username: string; + email: string; + password: string; + roleNames: string[]; + branchId?: string; +} + +export interface UpdateUserRequest { + email?: string; + branchId?: string; +} + +export interface ChangePasswordRequest { + currentPassword: string; + newPassword: string; +} + +export interface AssignRoleRequest { + roleName: string; +} + +export function createUsersResource(client: AxiosInstance) { + return { + async list(): Promise { + const response = await client.get(API_PATHS.users.base); + return response.data; + }, + + async getById(id: string): Promise { + const response = await client.get(API_PATHS.users.byId(id)); + return response.data; + }, + + async create(request: CreateUserRequest): Promise { + const response = await client.post(API_PATHS.users.base, request); + return response.data; + }, + + async update(id: string, request: UpdateUserRequest): Promise { + const response = await client.put(API_PATHS.users.byId(id), request); + return response.data; + }, + + async lock(id: string): Promise { + const response = await client.post(API_PATHS.users.lock(id)); + return response.data; + }, + + async unlock(id: string): Promise { + const response = await client.post(API_PATHS.users.unlock(id)); + return response.data; + }, + + async assignRole(id: string, request: AssignRoleRequest): Promise { + const response = await client.post(API_PATHS.users.roles(id), request); + return response.data; + }, + + async removeRole(id: string, roleName: string): Promise { + await client.delete(`${API_PATHS.users.roles(id)}/${encodeURIComponent(roleName)}`); + }, + + async changePassword(id: string, request: ChangePasswordRequest): Promise { + await client.put(API_PATHS.users.password(id), request); + }, + }; +} + +export type UsersResource = ReturnType; diff --git a/frontend/packages/api-client/src/token-provider.ts b/frontend/packages/api-client/src/token-provider.ts new file mode 100644 index 0000000..15500d0 --- /dev/null +++ b/frontend/packages/api-client/src/token-provider.ts @@ -0,0 +1,17 @@ +/** + * Interface for token storage and retrieval. + * The CLI implementation stores tokens in ~/.effigenix/config.json. + */ +export interface TokenProvider { + /** Returns the current access token, or null if not authenticated */ + getAccessToken(): Promise; + + /** Returns the current refresh token, or null if not authenticated */ + getRefreshToken(): Promise; + + /** Persists new tokens after a successful login or refresh */ + saveTokens(accessToken: string, refreshToken: string, expiresAt: string): Promise; + + /** Clears all stored tokens (called on logout or session expiry) */ + clearTokens(): Promise; +} diff --git a/frontend/packages/api-client/tsconfig.json b/frontend/packages/api-client/tsconfig.json new file mode 100644 index 0000000..84dd153 --- /dev/null +++ b/frontend/packages/api-client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": false, + "incremental": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/frontend/packages/config/package.json b/frontend/packages/config/package.json new file mode 100644 index 0000000..a133220 --- /dev/null +++ b/frontend/packages/config/package.json @@ -0,0 +1,33 @@ +{ + "name": "@effigenix/config", + "version": "0.1.0", + "description": "Shared configuration constants for Effigenix ERP", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist .turbo" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsup": "^8.0.1", + "typescript": "^5.3.3" + }, + "tsup": { + "entry": ["src/index.ts"], + "format": ["esm"], + "dts": true, + "clean": true, + "sourcemap": true, + "splitting": false + } +} diff --git a/frontend/packages/config/src/api-config.ts b/frontend/packages/config/src/api-config.ts new file mode 100644 index 0000000..51646c4 --- /dev/null +++ b/frontend/packages/config/src/api-config.ts @@ -0,0 +1,34 @@ +/** + * API client configuration + */ + +export interface ApiConfig { + baseUrl: string; + timeoutMs: number; + retries: number; +} + +export const DEFAULT_API_CONFIG: ApiConfig = { + baseUrl: 'http://localhost:8080', + timeoutMs: 10_000, + retries: 1, +}; + +export const API_PATHS = { + auth: { + login: '/api/auth/login', + logout: '/api/auth/logout', + refresh: '/api/auth/refresh', + }, + users: { + base: '/api/users', + byId: (id: string) => `/api/users/${id}`, + lock: (id: string) => `/api/users/${id}/lock`, + unlock: (id: string) => `/api/users/${id}/unlock`, + roles: (id: string) => `/api/users/${id}/roles`, + password: (id: string) => `/api/users/${id}/password`, + }, + roles: { + base: '/api/roles', + }, +} as const; diff --git a/frontend/packages/config/src/constants.ts b/frontend/packages/config/src/constants.ts new file mode 100644 index 0000000..693eb7f --- /dev/null +++ b/frontend/packages/config/src/constants.ts @@ -0,0 +1,26 @@ +/** + * Shared application constants + */ + +/** Milliseconds before token expiry at which a proactive refresh is triggered */ +export const TOKEN_REFRESH_THRESHOLD_MS = 5 * 60 * 1_000; // 5 minutes + +/** Default access token lifetime (matches backend default: 15 min) */ +export const ACCESS_TOKEN_LIFETIME_MS = 15 * 60 * 1_000; + +/** Default refresh token lifetime (matches backend default: 7 days) */ +export const REFRESH_TOKEN_LIFETIME_MS = 7 * 24 * 60 * 60 * 1_000; + +/** Path to the CLI config file */ +export const CLI_CONFIG_PATH = '~/.effigenix/config.json'; + +/** File permission octal for the CLI config file (owner read/write only) */ +export const CLI_CONFIG_FILE_MODE = 0o600; + +/** Pagination defaults */ +export const DEFAULT_PAGE_SIZE = 20; +export const MAX_PAGE_SIZE = 100; + +/** Password constraints (must match backend validation) */ +export const PASSWORD_MIN_LENGTH = 8; +export const PASSWORD_MAX_LENGTH = 128; diff --git a/frontend/packages/config/src/index.ts b/frontend/packages/config/src/index.ts new file mode 100644 index 0000000..211cba3 --- /dev/null +++ b/frontend/packages/config/src/index.ts @@ -0,0 +1,2 @@ +export * from './api-config.js'; +export * from './constants.js'; diff --git a/frontend/packages/config/tsconfig.json b/frontend/packages/config/tsconfig.json new file mode 100644 index 0000000..84dd153 --- /dev/null +++ b/frontend/packages/config/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": false, + "incremental": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/frontend/packages/types/package.json b/frontend/packages/types/package.json index 64f8c4a..3286bda 100644 --- a/frontend/packages/types/package.json +++ b/frontend/packages/types/package.json @@ -2,6 +2,7 @@ "name": "@effigenix/types", "version": "0.1.0", "description": "TypeScript types for Effigenix ERP (auto-generated from OpenAPI)", + "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { diff --git a/frontend/packages/types/tsconfig.json b/frontend/packages/types/tsconfig.json index 510f9b8..84dd153 100644 --- a/frontend/packages/types/tsconfig.json +++ b/frontend/packages/types/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "./dist", "rootDir": "./src", - "composite": true + "composite": false, + "incremental": false }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] diff --git a/frontend/packages/validation/package.json b/frontend/packages/validation/package.json new file mode 100644 index 0000000..decee84 --- /dev/null +++ b/frontend/packages/validation/package.json @@ -0,0 +1,37 @@ +{ + "name": "@effigenix/validation", + "version": "0.1.0", + "description": "Zod validation schemas for Effigenix ERP", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist .turbo" + }, + "dependencies": { + "@effigenix/config": "workspace:*", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsup": "^8.0.1", + "typescript": "^5.3.3" + }, + "tsup": { + "entry": ["src/index.ts"], + "format": ["esm"], + "dts": true, + "clean": true, + "sourcemap": true, + "splitting": false + } +} diff --git a/frontend/packages/validation/src/index.ts b/frontend/packages/validation/src/index.ts new file mode 100644 index 0000000..bc839e7 --- /dev/null +++ b/frontend/packages/validation/src/index.ts @@ -0,0 +1,3 @@ +export * from './validators.js'; +export * from './schemas/auth.js'; +export * from './schemas/user.js'; diff --git a/frontend/packages/validation/src/schemas/auth.ts b/frontend/packages/validation/src/schemas/auth.ts new file mode 100644 index 0000000..74d430b --- /dev/null +++ b/frontend/packages/validation/src/schemas/auth.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; +import { usernameSchema } from '../validators.js'; + +export const loginRequestSchema = z.object({ + username: usernameSchema, + password: z.string().min(1, 'Passwort ist erforderlich'), +}); + +export const refreshTokenRequestSchema = z.object({ + refreshToken: z.string().min(1, 'Refresh Token ist erforderlich'), +}); + +export type LoginRequestInput = z.input; +export type LoginRequestOutput = z.output; diff --git a/frontend/packages/validation/src/schemas/user.ts b/frontend/packages/validation/src/schemas/user.ts new file mode 100644 index 0000000..81ce21c --- /dev/null +++ b/frontend/packages/validation/src/schemas/user.ts @@ -0,0 +1,37 @@ +import { z } from 'zod'; +import { emailSchema, passwordSchema, usernameSchema, uuidSchema } from '../validators.js'; + +export const createUserRequestSchema = z.object({ + username: usernameSchema, + email: emailSchema, + password: passwordSchema, + roleIds: z.array(uuidSchema).min(1, 'Mindestens eine Rolle muss zugewiesen werden'), + branchId: uuidSchema.optional(), +}); + +export const updateUserRequestSchema = z.object({ + email: emailSchema.optional(), + branchId: uuidSchema.optional(), +}); + +export const changePasswordRequestSchema = z + .object({ + currentPassword: z.string().min(1, 'Aktuelles Passwort ist erforderlich'), + newPassword: passwordSchema, + confirmPassword: z.string().min(1, 'Passwortbestätigung ist erforderlich'), + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: 'Passwörter stimmen nicht überein', + path: ['confirmPassword'], + }); + +export const assignRoleRequestSchema = z.object({ + roleId: uuidSchema, +}); + +export type CreateUserRequestInput = z.input; +export type CreateUserRequestOutput = z.output; +export type UpdateUserRequestInput = z.input; +export type UpdateUserRequestOutput = z.output; +export type ChangePasswordRequestInput = z.input; +export type AssignRoleRequestInput = z.input; diff --git a/frontend/packages/validation/src/validators.ts b/frontend/packages/validation/src/validators.ts new file mode 100644 index 0000000..781785d --- /dev/null +++ b/frontend/packages/validation/src/validators.ts @@ -0,0 +1,28 @@ +import { z } from 'zod'; +import { PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH } from '@effigenix/config'; + +/** + * Reusable field validators + */ + +export const emailSchema = z + .string() + .min(1, 'E-Mail ist erforderlich') + .email('Ungültige E-Mail-Adresse') + .max(254, 'E-Mail-Adresse zu lang'); + +export const passwordSchema = z + .string() + .min(PASSWORD_MIN_LENGTH, `Passwort muss mindestens ${String(PASSWORD_MIN_LENGTH)} Zeichen lang sein`) + .max(PASSWORD_MAX_LENGTH, `Passwort darf maximal ${String(PASSWORD_MAX_LENGTH)} Zeichen lang sein`) + .refine((val) => /[A-Z]/.test(val), 'Passwort muss mindestens einen Großbuchstaben enthalten') + .refine((val) => /[a-z]/.test(val), 'Passwort muss mindestens einen Kleinbuchstaben enthalten') + .refine((val) => /[0-9]/.test(val), 'Passwort muss mindestens eine Ziffer enthalten'); + +export const usernameSchema = z + .string() + .min(3, 'Benutzername muss mindestens 3 Zeichen lang sein') + .max(50, 'Benutzername darf maximal 50 Zeichen lang sein') + .regex(/^[a-zA-Z0-9_.-]+$/, 'Benutzername darf nur Buchstaben, Ziffern, _, . und - enthalten'); + +export const uuidSchema = z.string().uuid('Ungültige UUID'); diff --git a/frontend/packages/validation/tsconfig.json b/frontend/packages/validation/tsconfig.json new file mode 100644 index 0000000..84dd153 --- /dev/null +++ b/frontend/packages/validation/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "composite": false, + "incremental": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..0cda17a --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3708 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@typescript-eslint/eslint-plugin': + specifier: ^7.0.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^7.0.0 + version: 7.18.0(eslint@8.57.1)(typescript@5.9.3) + eslint: + specifier: ^8.56.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.2(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.1) + prettier: + specifier: ^3.2.5 + version: 3.8.1 + typescript: + specifier: ^5.3.3 + version: 5.9.3 + + apps/cli: + dependencies: + '@effigenix/api-client': + specifier: workspace:* + version: link:../../packages/api-client + '@effigenix/config': + specifier: workspace:* + version: link:../../packages/config + '@effigenix/types': + specifier: workspace:* + version: link:../../packages/types + '@effigenix/validation': + specifier: workspace:* + version: link:../../packages/validation + ink: + specifier: ^5.0.1 + version: 5.2.1(@types/react@18.3.28)(react@18.3.1) + ink-text-input: + specifier: ^6.0.0 + version: 6.0.0(ink@5.2.1(@types/react@18.3.28)(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.2.0 + version: 18.3.1 + yargs: + specifier: ^17.7.2 + version: 17.7.2 + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.33 + '@types/react': + specifier: ^18.2.0 + version: 18.3.28 + '@types/yargs': + specifier: ^17.0.32 + version: 17.0.35 + ink-testing-library: + specifier: ^4.0.0 + version: 4.0.0(@types/react@18.3.28) + tsup: + specifier: ^8.0.1 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.3 + version: 5.9.3 + vitest: + specifier: ^1.2.0 + version: 1.6.1(@types/node@20.19.33) + + packages/api-client: + dependencies: + '@effigenix/config': + specifier: workspace:* + version: link:../config + '@effigenix/types': + specifier: workspace:* + version: link:../types + axios: + specifier: ^1.6.7 + version: 1.13.5 + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.33 + axios-mock-adapter: + specifier: ^1.22.0 + version: 1.22.0(axios@1.13.5) + tsup: + specifier: ^8.0.1 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + vitest: + specifier: ^1.2.2 + version: 1.6.1(@types/node@20.19.33) + + packages/config: + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.33 + tsup: + specifier: ^8.0.1 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + + packages/types: + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.33 + openapi-typescript: + specifier: ^7.4.2 + version: 7.13.0(typescript@5.9.3) + tsup: + specifier: ^8.0.1 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + + packages/validation: + dependencies: + '@effigenix/config': + specifier: workspace:* + version: link:../config + zod: + specifier: ^3.22.4 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.33 + tsup: + specifier: ^8.0.1 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.3.3 + version: 5.9.3 + +packages: + + '@alcalzone/ansi-tokenize@0.1.3': + resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} + engines: {node: '>=14.13.1'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@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.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + 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.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + 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.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + 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.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + 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.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + 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.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + 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.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + 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.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + 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.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + 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.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + 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.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + 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.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + 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.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + 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} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@redocly/ajv@8.17.4': + resolution: {integrity: sha512-BieiCML/IgP6x99HZByJSt7fJE4ipgzO7KAFss92Bs+PEI35BhY7vGIysFXLT+YmS7nHtQjZjhOQyPPEf7xGHA==} + + '@redocly/config@0.22.2': + resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} + + '@redocly/openapi-core@1.34.6': + resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@20.19.33': + resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitest/expect@1.6.1': + resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + + '@vitest/runner@1.6.1': + resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + + '@vitest/snapshot@1.6.1': + resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + + '@vitest/spy@1.6.1': + resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + + '@vitest/utils@1.6.1': + resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + axios-mock-adapter@1.22.0: + resolution: {integrity: sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==} + peerDependencies: + axios: '>= 0.17.0' + + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + 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' + + 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'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + 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'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + 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'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + 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@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ink-testing-library@4.0.0: + resolution: {integrity: sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=18.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + + ink-text-input@6.0.0: + resolution: {integrity: sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==} + engines: {node: '>=18'} + peerDependencies: + ink: '>=5' + react: '>=18' + + ink@5.2.1: + resolution: {integrity: sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=18.0.0' + react: '>=18.0.0' + react-devtools-core: ^4.19.1 + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ci@1.0.0: + resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} + engines: {node: '>=18'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + 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} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + 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} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + + patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + 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@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-reconciler@0.29.2: + resolution: {integrity: sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + 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==} + + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.1: + resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + 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==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + 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 + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@1.6.1: + resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.1 + '@vitest/ui': 1.6.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@alcalzone/ansi-tokenize@0.1.3': + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3(supports-color@10.2.2) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3(supports-color@10.2.2) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@pkgr/core@0.2.9': {} + + '@redocly/ajv@8.17.4': + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + '@redocly/config@0.22.2': {} + + '@redocly/openapi-core@1.34.6(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.17.4 + '@redocly/config': 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@sinclair/typebox@0.27.10': {} + + '@types/estree@1.0.8': {} + + '@types/node@20.19.33': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.3(supports-color@10.2.2) + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3(supports-color@10.2.2) + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.3(supports-color@10.2.2) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@vitest/expect@1.6.1': + dependencies: + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + chai: 4.5.0 + + '@vitest/runner@1.6.1': + dependencies: + '@vitest/utils': 1.6.1 + p-limit: 5.0.0 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.1': + dependencies: + magic-string: 0.30.21 + pathe: 1.1.2 + pretty-format: 29.7.0 + + '@vitest/spy@1.6.1': + dependencies: + tinyspy: 2.2.1 + + '@vitest/utils@1.6.1': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + assertion-error@1.1.0: {} + + asynckit@0.4.0: {} + + auto-bind@5.0.1: {} + + axios-mock-adapter@1.22.0(axios@1.13.5): + dependencies: + axios: 1.13.5 + fast-deep-equal: 3.1.3 + is-buffer: 2.0.5 + + axios@1.13.5: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bundle-require@5.1.0(esbuild@0.27.3): + dependencies: + esbuild: 0.27.3 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + change-case@5.4.4: {} + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cli-boxes@3.0.0: {} + + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + code-excerpt@4.0.0: + dependencies: + convert-to-spaces: 2.0.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@1.4.0: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-to-spaces@2.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + diff-sequences@29.6.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + environment@1.1.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-toolkit@1.44.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + 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-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.1): + dependencies: + eslint: 8.57.1 + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + eslint-config-prettier: 9.1.2(eslint@8.57.1) + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@10.2.2) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + 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@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + human-signals@5.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@5.0.0: {} + + index-to-position@1.2.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ink-testing-library@4.0.0(@types/react@18.3.28): + optionalDependencies: + '@types/react': 18.3.28 + + ink-text-input@6.0.0(ink@5.2.1(@types/react@18.3.28)(react@18.3.1))(react@18.3.1): + dependencies: + chalk: 5.6.2 + ink: 5.2.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + type-fest: 4.41.0 + + ink@5.2.1(@types/react@18.3.28)(react@18.3.1): + dependencies: + '@alcalzone/ansi-tokenize': 0.1.3 + ansi-escapes: 7.3.0 + ansi-styles: 6.2.3 + auto-bind: 5.0.1 + chalk: 5.6.2 + cli-boxes: 3.0.0 + cli-cursor: 4.0.0 + cli-truncate: 4.0.0 + code-excerpt: 4.0.0 + es-toolkit: 1.44.0 + indent-string: 5.0.0 + is-in-ci: 1.0.0 + patch-console: 2.0.0 + react: 18.3.1 + react-reconciler: 0.29.2(react@18.3.1) + scheduler: 0.23.2 + signal-exit: 3.0.7 + slice-ansi: 7.1.2 + stack-utils: 2.0.6 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + ws: 8.19.0 + yoga-layout: 3.2.1 + optionalDependencies: + '@types/react': 18.3.28 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + is-buffer@2.0.5: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ci@1.0.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-stream@3.0.0: {} + + isexe@2.0.0: {} + + joycon@3.1.1: {} + + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + local-pkg@0.5.1: + dependencies: + mlly: 1.8.0 + pkg-types: 1.3.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-assign@4.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.6(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@5.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + patch-console@2.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@1.1.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.21.0): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.6 + tsx: 4.21.0 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + react-reconciler@0.29.2(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-literal@2.1.1: + dependencies: + js-tokens: 9.0.1 + + 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 + + supports-color@10.2.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@0.8.4: {} + + tinyspy@2.2.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tree-kill@1.2.2: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsup@8.5.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(supports-color@10.2.2) + 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(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: + 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 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.1.0: {} + + type-fest@0.20.2: {} + + type-fest@4.41.0: {} + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@1.6.1(@types/node@20.19.33): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@10.2.2) + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@20.19.33) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@20.19.33): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + '@types/node': 20.19.33 + fsevents: 2.3.3 + + vitest@1.6.1(@types/node@20.19.33): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.4 + chai: 4.5.0 + debug: 4.4.3(supports-color@10.2.2) + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.21 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.10.0 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.21(@types/node@20.19.33) + vite-node: 1.6.1(@types/node@20.19.33) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.33 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + y18n@5.0.8: {} + + yaml-ast-parser@0.0.43: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + yoga-layout@3.2.1: {} + + zod@3.25.76: {} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..788a92f --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + name = "effigenix-frontend"; + + buildInputs = with pkgs; [ + nodejs_22 + nodePackages.pnpm + + # Optional: useful CLI tools during development + jq # JSON processing for manual API testing + curl # Manual HTTP requests + ]; + + shellHook = '' + echo "Node $(node --version)" + echo "npm $(npm --version)" + echo "pnpm $(pnpm --version)" + echo "" + echo "Run 'pnpm install' to install dependencies." + echo "Run 'pnpm run build' to build all packages." + echo "Run 'pnpm run dev' to start the TUI in watch mode." + ''; +}