diff --git a/mobile/.gitignore b/mobile/.gitignore deleted file mode 100644 index 4cb7853..0000000 --- a/mobile/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -.kotlin/ -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision -.env - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -# generated native folders -/ios -/android - -modules/*/android/build/ -modules/*/android/.gradle/ \ No newline at end of file diff --git a/mobile/AGENTS.md b/mobile/AGENTS.md deleted file mode 100644 index dee2fb5..0000000 --- a/mobile/AGENTS.md +++ /dev/null @@ -1,30 +0,0 @@ -# Expo HAS CHANGED - -Read the exact versioned docs at https://docs.expo.dev/versions/v57.0.0/ before writing any code. - -# Local storage - -Persistence is SQLite-backed via `services/db/` (`expo-sqlite`, database `dot.db`, `user_version` = 4). - -- `services/db/client.ts` — connection lifecycle: `getDatabase()` (single-flight), `closeDatabase()`, `withTransaction()`. Android-only (expo-sqlite#48999, unfixed in 57.x): on a dead NPE-like connection, `recoverDatabase()` drops the poisoned handle and reopens via `openDatabaseAsync(name, { useNewConnection: true })`; `withDatabaseRetry()` wraps any `(db) → Promise` with one automatic recovery+retry; `DatabaseRetryDeps` seam allows unit-testing under plain Node. -- `services/db/migrations.ts` — versioned, chained migrations via `PRAGMA user_version` (list of `{ version, up }`), single-flight `WeakMap` lock, `migrateDatabase(db, targetVersion?)`. v4 is a **transactional rebuild** (folders/files drop `uri` keys, gain `resource_id` + partial unique index on non-null `uri`). Migration tests: `npm run test:migrations` (better-sqlite3 harness in `tests/migrations.test.ts`); repository tests: `npm run test:db` (both suites, better-sqlite3 via the `DbSession` seam); recovery tests: `tests/dbClient.test.ts` (isBrokenConnectionError + withDatabaseRetry via `DatabaseRetryDeps`, included in `test:db`). -- `services/db/session.ts` — `DbSession` (injectable `runAsync`/`getFirstAsync`/`getAllAsync`) ; repos get it through `getSession()`, tests override it with `__setDbForTests()`. `client.ts` loads `expo-sqlite` lazily so the test suites run under plain Node. `liveSession` wraps each method with `withDatabaseRetry` to auto-recover on the expo-sqlite NPE. -- `services/db/schema.ts` — `DATABASE_NAME`, `DATABASE_VERSION`, column-list constants, `DEVICE_USER_ID_KEY`, `PERMISSION_TTL_MS` (24h offline stale-cache). -- `services/db/id.ts` — `newResourceId()` opaque 32-hex `lower(hex(randomblob(16)))`, generated per row. -- `services/db/transitions.ts` — `transitionSyncStatus(from, event)`: per-row sync status transitions. -- `services/db/repositories/` — one module per table: `folders`, `files`, `user_preferences` (+ `getDeviceUserId`), `resource_permissions` (`permissions.ts` with `canAccess`/`canWrite`/`isOwner`, hierarchical via `WITH RECURSIVE`, `inherit`/`expires_at` honored, 24h stale-cache read-only downgrade), `shares`, `share_links`, `recipients`, `pending_operations` (`pendingOps.ts`, outbox: FIFO on `(created_at, id)`, failure schedules a `pending` retry with backoff, dead-letter `failed` after `MAX_PENDING_ATTEMPTS`). -- `services/localStorage.ts` is a thin re-export (`services/db`) kept for legacy imports. -- Tables: `folders`, `files`, `user_preferences`, `resource_permissions`, `shares`, `share_links`, `recipients`, `pending_operations`. -- Canonical identity: `resource_id` (opaque, unique) on folders/files/shares/share_links; `uri` (physical SAF path) is **nullable**, NULL = cloud-only; `owner_id` NOT NULL seeded from `device_user_id`. -- Folder and file per-row sync status: `local` | `cloud` | `local-cloud` (placement state, transitions via `transitionSyncStatus`). -- `shares`/`share_links` have NO `sync_status`: their `pushStatus` (`pending`/`synced`/`failed`) is **derived** from `pending_operations` (`ref_type` = `share`|`share_link`, `ref_id`). -- Query usage: `getFiles(folderResourceId?)`, `getFolders()`, `getFolderFolders(parentResourceId)`, `getFolder`/`getFile(resourceId)`, `saveFolder`/`saveDirectory`, `saveFile(file, folderResourceId)`, `removeFolder`/`removeFile(resourceId)`, `saveUserPreferences`/`getUserPreferences`/`getDeviceUserId`, `saveResourcePermission`/`getResourcePermission`, `canAccess(resourceId, type, level)`, `saveShare`/`getShares`/`removeShare`, `createShareLink`/`getShareLinks`/`incrementLinkDownloads`/`revokeShareLink`, `saveRecipient`/`getRecipients`, `enqueuePendingOperation`/`getNextQueuedOperation`/`listQueuedOperations`/`scheduleRetries`/`markPendingOperation`/`MAX_PENDING_ATTEMPTS`. -- SAF walk (`features/syncDevice.ts`, `syncDevice()`/`syncRoot()`): **two passes** (all folders sorted by uri depth, then all files) inside a **single transaction** (`withTransaction`), receives sync: an interruption rolls back entirely. Reconciles by physical `uri`; rows under the root with a `uri` no longer seen are **marked `exists = 0`** (never deleted). Root folders are the rows with `parent_resource_id IS NULL` + non-null `uri`. -- Sync loop (`features/syncDevice.ts` → `useSyncDevice()`): after each SAF walk the same tick runs `features/syncOutbox.ts` — `pushPendingOps()` (outbox → `POST /sync/ops`) then `refreshPermissions()` (delta snapshot `GET /sync/permissions`). Both are **no-ops without an auth token** (`hasAuthToken()`, set after `POST /devices`). -- Outbox push semantics (`pushPendingOps`): batch = first `SYNC_BATCH_SIZE` (50) rows FIFO due (`next_retry_at <= now`) via `listQueuedOperations`. Server replies a **single `applied` index** (see `SyncResult` comment): ops `[0, applied)` → `markPendingOperation('completed')`; the op at `applied` when `failed` is non-null → `markPendingOperation('failed')` (backoff, dead-letter at `MAX_PENDING_ATTEMPTS`); ops after it stay `pending` and are re-sent next tick. **Transient** network/5xx errors → `scheduleRetries` bumps only `next_retry_at` (**never `attempts`**) — a failed batch is not dead-lettered prematurely; `applied == 0 && failed != null` means nothing was committed (1st op refused), no counters touched beyond the single backoff. -- Permissions snapshot (`refreshPermissions`): in-memory monotone `lastPermissionCachedAt` (max server `cachedAt`) becomes the `after` query param of the next call; rows are upserted via `saveResourcePermission` (24h TTL + read-only downgrade enforced by `canAccess`). Unit tests: `npm run test:sync`; live smoke (needs backend + postgres): `npm run test:e2e` (skips when the server is down, NOT part of `npm test`). -- Heavy processing stays server-side; SQLite only persists local metadata/state. - -# REST API client - -`api/client.ts` + `api/types.ts` = the client-side **API contract** (server must implement it; backend Go is the source of truth once built). Base URL = `EXPO_PUBLIC_API_BASE_URL` (défaut `http://localhost:8080/api/v1`). Envelope: success `{ data, meta?: { page, pageSize, total } }`, errors normalized to `ApiError` (`code` from `{ error: { code, message } }`, or `NETWORK_ERROR` / `HTTP_` / `INVALID_RESPONSE` for 2xx bodies without a valid envelope). Multipart upload needs the platform FormData (uri/name/type) — never set `Content-Type` manually. TanStack Query v5 providers live in `app/_layout.tsx`; hooks in `hooks/` (`useFiles`, `useSearch`, `useUpload`, OCR jobs). diff --git a/mobile/LICENSE b/mobile/LICENSE deleted file mode 100644 index 30b20e3..0000000 --- a/mobile/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/mobile/api/client.ts b/mobile/api/client.ts deleted file mode 100644 index 60c13c6..0000000 --- a/mobile/api/client.ts +++ /dev/null @@ -1,226 +0,0 @@ -import type { - ApiData, - ApiErrorBody, - DeviceRegistration, - FileDto, - FolderDto, - ListFilesParams, - LoginResponse, - OcrJob, - ResolvedUser, - ResourcePermission, - SyncOperation, - SyncResult, -} from './types'; - -const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL ?? 'http://localhost:8080/api/v1'; - -export const DEFAULT_TIMEOUT_MS = 15_000; - -let authToken: string | null = null; - -export function setAuthToken(token: string | null): void { - authToken = token; -} - -export function hasAuthToken(): boolean { - return authToken !== null; -} - -// Notification de 401 reçus sur un endpoint protégé (hors login lui-même) — -// AuthContext s'en sert pour purger la session (token révoqué/expiré). -let onUnauthorized: (() => void) | null = null; - -export function setUnauthorizedHandler(handler: (() => void) | null): void { - onUnauthorized = handler; -} - -type QueryParams = Record; - -function toQuery(params?: QueryParams): string { - if ( !params ) return ''; - const search = new URLSearchParams(); - for ( const [key, value] of Object.entries(params) ) { - if ( value === undefined || value === null ) continue; - search.set(key, String(value)); - } - const query = search.toString(); - return query ? `?${query}` : ''; -} - -function mergeHeaders(init?: HeadersInit): HeadersInit | undefined { - if ( !authToken ) return init; - const merged = new Headers(init); - if ( !merged.has('Authorization') ) { - merged.set('Authorization', `Bearer ${authToken}`); - } - return merged; -} - -export class ApiError extends Error { - readonly code: string; - - constructor(code: string, message: string) { - super(message); - this.name = 'ApiError'; - this.code = code; - } -} - -async function request( - path: string, - init: RequestInit = {}, - timeoutMs: number = DEFAULT_TIMEOUT_MS, - opts: { skipUnauthorizedHandling?: boolean } = {}, -): Promise> { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - let response: Response; - try { - response = await fetch(`${API_BASE_URL}${path}`, { - ...init, - headers: mergeHeaders(init.headers), - signal: controller.signal, - }); - } catch { - throw new ApiError('NETWORK_ERROR', 'Serveur injoignable'); - } - - if (response.status === 401 && !opts.skipUnauthorizedHandling) { - onUnauthorized?.(); - } - - const body = (await response.json().catch(() => null)) as - | ApiData - | ApiErrorBody - | unknown[] - | string - | number - | null; - - if ( !response.ok ) { - const error = body != null && isErrorBody(body) ? body.error : null; - throw new ApiError( - error?.code ?? `HTTP_${response.status}`, - error?.message ?? response.statusText - ); - } - - // 2xx : enveloppe `{ data }` obligatoire. Un corps illisible (HTML d'un - // proxy, JSON malformé, corps vide) ou un objet sans clé `data` est une - // réponse invalide → ApiError propre, jamais de TypeError ni de data - // indéfini silencieux. Les listes brutes (`[]`) restent tolérées. - if ( body === null || isErrorBody(body) ) { - throw new ApiError('INVALID_RESPONSE', 'Réponse serveur invalide (enveloppe {data} attendue)'); - } - if ( !isRecord(body) ) { - return { data: body as T }; - } - if ( !('data' in body) ) { - throw new ApiError('INVALID_RESPONSE', 'Réponse serveur invalide (enveloppe {data} attendue)'); - } - return body as ApiData; - } finally { - clearTimeout(timer); - } -} - -function isErrorBody(body: unknown): body is ApiErrorBody { - return ( - typeof body === 'object' && - body !== null && - 'error' in body && - (body as { error?: { code?: unknown } }).error != null - ); -} - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -export const api = { - getApiBaseUrl: () => API_BASE_URL, - - health: () => request<{ status: string }>('/health'), - - registerDevice: (deviceId: string) => - request('/devices', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ deviceId }), - }), - - // Seule porte d'émission de token (V1). Le 401 = mauvaises identifiants - // (normal sur l'écran de login) : on ne déclenche pas la purge de session. - login: (username: string, password: string, deviceId: string) => - request('/auth/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password, device_id: deviceId }), - }, DEFAULT_TIMEOUT_MS, { skipUnauthorizedHandling: true }), - - changePassword: (currentPassword: string, newPassword: string) => - request<{ id: string }>('/users/me/password', { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }), - }), - - // Résolution exacte d'un destinataire par username — jamais de listing. - resolveUser: (username: string) => - request(`/users/resolve${toQuery({ username })}`), - - listFiles: (params?: ListFilesParams) => - request(`/files${toQuery(params)}`), - - getFile: (id: string) => - request(`/files/${encodeURIComponent(id)}`), - - deleteFile: (id: string) => - request<{ id: string }>(`/files/${encodeURIComponent(id)}`, { - method: 'DELETE', - }), - - searchFiles: (q: string, params?: QueryParams) => - request(`/files/search${toQuery({ q, ...params })}`), - - listFolders: () => request('/files/folders'), - - uploadFile: ( - file: { uri: string; name: string; mimeType: string }, - folderId?: string | null - ) => { - const form = new FormData(); - form.append('file', { - uri: file.uri, - name: file.name, - type: file.mimeType, - } as unknown as Blob); - if ( folderId ) form.append('folderId', folderId); - return request('/files/upload', { - method: 'POST', - body: form, - }); - }, - - createOcrJob: (fileId: string) => - request('/ocr/jobs', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileId }), - }), - - getOcrJob: (id: string) => - request(`/ocr/jobs/${encodeURIComponent(id)}`), - - syncOps: (operations: SyncOperation[]) => - request('/sync/ops', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ operations }), - }), - - getSyncPermissions: (after?: number) => - request(`/sync/permissions${after != null ? `?after=${after}` : ''}`), -}; \ No newline at end of file diff --git a/mobile/api/types.ts b/mobile/api/types.ts deleted file mode 100644 index e2060a6..0000000 --- a/mobile/api/types.ts +++ /dev/null @@ -1,119 +0,0 @@ -export type ApiMeta = { - page: number; - pageSize: number; - total: number; -}; - -export type ApiData = { - data: T; - meta?: ApiMeta; -}; - -export type ApiErrorBody = { - error: { - code: string; - message: string; - }; -}; - -export type FileDto = { - id: string; - name: string; - size: number; - mimeType?: string | null; - folderId?: string | null; - tags?: string[]; - createdAt?: string; - updatedAt?: string; -}; - -export type FolderDto = { - id: string; - name: string; - parentId?: string | null; -}; - -export type OcrJobStatus = 'queued' | 'processing' | 'done' | 'failed'; - -export type OcrJob = { - id: string; - status: OcrJobStatus; - text?: string | null; - error?: string | null; -}; - -export type DeviceRegistration = { - deviceId: string; -}; - -export type User = { - id: string; - username: string; - is_admin: boolean; -}; - -export type LoginRequest = { - username: string; - password: string; - device_id: string; -}; - -export type LoginResponse = { - token: string; - expires_at: number; - user: User; -}; - -export type ResolvedUser = { - id: string; - username: string; -}; - -export type ListParams = { - page?: number; - pageSize?: number; - sort?: string; -}; - -export type ListFilesParams = ListParams & { - folderId?: string | null; -}; - -// Miroir de docs/api-v1.md §6.1 — une entrée d'outbox côté serveur. -// `operation_id` = `pending_operations.id` local (INTEGER). -export type SyncOperation = { - operation_id: number; - ref_type?: 'resource' | 'share' | 'share_link' | null; - ref_id?: number | null; - resource_id: string; - resource_type: 'folder' | 'file'; - operation: string; - payload: Record; -}; - -// `applied` est un INDEX, pas un compte : nombre d'ops commitées depuis le -// début du batch. -// - succès → applied == operations.length, failed == null -// - échec → applied == index de l'op refusée (< length), -// failed = cette op ; reprise à operations.slice(applied) -// - applied == 0 → rien de commité (1re op refusée, `failed` non-null, ou -// batch vide) : tout est à rejouer, aucun compteur à -// incrémenter côté client. -// Invariant serveur : jamais applied == length avec failed non-null. -export type SyncResult = { - applied: number; - failed: { operation_id: number; code: string; message: string } | null; -}; - -// Miroir de docs/api-v1.md §6.2 — consommé tel quel par `saveResourcePermission`. -export type ResourcePermission = { - resource_id: string; - resourceType: 'folder' | 'file'; - effectiveAccess: 'viewer' | 'commenter' | 'editor' | 'owner'; - inherit: boolean; - ownerId: string | null; - sharedById: string | null; - expiresAt: number | null; - cachedAt: number; - updatedAt: number; -}; \ No newline at end of file diff --git a/mobile/app.json b/mobile/app.json deleted file mode 100644 index 1d93fae..0000000 --- a/mobile/app.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "expo": { - "name": "webui", - "slug": "webui", - "scheme": "webui", - "version": "2.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.webui" - }, - "android": { - "adaptiveIcon": { - "backgroundColor": "#E6F4FE", - "foregroundImage": "./assets/android-icon-foreground.png", - "backgroundImage": "./assets/android-icon-background.png", - "monochromeImage": "./assets/android-icon-monochrome.png" - }, - "package": "com.anonymous.webui" - }, - "web": { - "favicon": "./assets/favicon.png" - }, - "plugins": [ - "expo-router", - "expo-sqlite", - "expo-localization", - "expo-secure-store" - ] - } -} diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx deleted file mode 100644 index 452c39c..0000000 --- a/mobile/app/_layout.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { Redirect, Stack } from 'expo-router'; -import { ActivityIndicator, View } from 'react-native'; -import { AuthProvider, useAuth } from '../context/AuthContext'; -import i18n from '../i18n'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30_000, - retry: 2, - }, - }, -}); - -function AuthGate() { - const { status } = useAuth(); - - if (status === 'loading') { - return ( - - - - ); - } - - return ( - <> - - - - - - - - {/* Rediriger vers la connexion uniquement si l'utilisateur n'a pas de session ET n'est pas en mode local. */} - {status === 'signedOut' ? : null} - - ); -} - -export default function RootLayout() { - return ( - - - - - - ); -} \ No newline at end of file diff --git a/mobile/app/folder/[id].tsx b/mobile/app/folder/[id].tsx deleted file mode 100644 index adbd6ec..0000000 --- a/mobile/app/folder/[id].tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Stack, useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router'; -import { useCallback, useState } from 'react'; -import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native'; -import { getFolder, getFolderFolders, getFiles } from '../../services/localStorage'; -import type { StoredFile, StoredFolder } from '../../services/db/types'; -import i18n from '../../i18n'; - -type Row = { - key: string; - kind: 'folder' | 'file'; - label: string; - meta: string; - resourceId: string; -}; - -function formatSize(bytes: number): string { - if ( bytes < 1024 ) return `${bytes} ${i18n.t('bytes')}`; - if ( bytes < 1024 * 1024 ) return `${(bytes / 1024).toFixed(1)} ${i18n.t('kilobytes')}`; - return `${(bytes / (1024 * 1024)).toFixed(1)} ${i18n.t('megabytes')}`; -} - -export default function FolderScreen() { - - const router = useRouter(); - const { id } = useLocalSearchParams<{ id: string }>(); - const [folder, setFolder] = useState(null); - const [subfolders, setSubfolders] = useState([]); - const [files, setFiles] = useState([]); - - const load = useCallback(async () => { - const current = await getFolder(id); - if ( !current ) { - router.back(); - return; - } - setFolder(current); - setSubfolders((await getFolderFolders(id)).filter(f => f.exists)); - setFiles((await getFiles(id)).filter(f => f.exists)); - }, [id, router]); - - useFocusEffect( - useCallback(() => { - load(); - }, [load]) - ); - - const rows: Row[] = [ - ...subfolders.map((f) => ({ - key: f.resource_id, - kind: 'folder' as const, - label: f.name, - meta: i18n.t('folder'), - resourceId: f.resource_id, - })), - ...files.map((f) => ({ - key: f.resource_id, - kind: 'file' as const, - label: f.name, - meta: formatSize(f.size), - resourceId: f.resource_id, - })), - ]; - - return ( - - - item.key} - renderItem={({ item }) => ( - item.kind === 'folder' && router.push(`/folder/${item.resourceId}`)} - > - - {item.kind === 'folder' ? `${item.label}/` : item.label} - - {item.meta} - - )} - ListEmptyComponent={ - - {i18n.t('empty_folder')} - - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - padding: 16, - }, - row: { - paddingVertical: 14, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: '#ddd', - }, - folderTitle: { - fontSize: 16, - fontWeight: '600', - color: '#1a73e8', - }, - fileTitle: { - fontSize: 16, - }, - rowMeta: { - fontSize: 12, - color: '#888', - marginTop: 2, - }, - empty: { - color: '#888', - textAlign: 'center', - marginTop: 24, - }, -}); \ No newline at end of file diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx deleted file mode 100644 index efb4207..0000000 --- a/mobile/app/index.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { Stack, useFocusEffect } from 'expo-router'; -import { StatusBar } from 'expo-status-bar'; -import { Ionicons } from '@expo/vector-icons'; -import { useCallback, useState } from 'react'; -import { Pressable, SectionList, StyleSheet, Text, View } from 'react-native'; -import { syncRoot } from '../features/syncDevice'; -import { pickDirectory } from '../services/safDirectory'; -import { getFiles, saveDirectory } from '../services/localStorage'; -import type { StoredFile } from '../services/db/types'; -import FloatingNavBar from '../components/FloatingNavBar'; -import i18n from '../i18n'; - -type FilePair = { - key: string; - left: StoredFile; - right: StoredFile | null; -}; - -type FileSection = { - key: string; - dayLabel: string; - data: FilePair[]; -}; - -function formatSize(bytes: number): string { - if ( bytes < 1024 ) return `${bytes} ${i18n.t('bytes')}`; - if ( bytes < 1024 * 1024 ) return `${(bytes / 1024).toFixed(1)} ${i18n.t('kilobytes')}`; - return `${(bytes / (1024 * 1024)).toFixed(1)} ${i18n.t('megabytes')}`; -} - -function formatDay(timestamp: number): string { - return new Intl.DateTimeFormat(i18n.locale, { day: 'numeric', month: 'long', year: 'numeric' }).format(timestamp); -} - -function startOfDay(timestamp: number): number { - const date = new Date(timestamp); - date.setHours(0, 0, 0, 0); - return date.getTime(); -} - -function chunkToPairs(files: StoredFile[]): FilePair[] { - const pairs: FilePair[] = []; - for ( let i = 0; i < files.length; i += 2 ) { - pairs.push({ - key: files[i].resource_id + (files[i + 1]?.resource_id ?? ''), - left: files[i], - right: files[i + 1] ?? null, - }); - } - return pairs; -} - -function groupFilesByDay(files: StoredFile[]): FileSection[] { - const byDay = new Map(); - for ( const file of files ) { - const day = startOfDay(file.addedAt); - const bucket = byDay.get(day); - if ( bucket ) bucket.push(file); - else byDay.set(day, [file]); - } - return [...byDay.entries()] - .sort((a, b) => b[0] - a[0]) - .map(([day, data]) => ({ - key: String(day), - dayLabel: formatDay(day), - data: chunkToPairs(data.sort((a, b) => b.addedAt - a.addedAt)), - })); -} - -function FileCard({ file }: { file: StoredFile }) { - return ( - - - {file.name} - {formatSize(file.size)} - - ); -} - -export default function Index() { - - const [sections, setSections] = useState([]); - - const load = useCallback(async () => { - const files = (await getFiles()).filter(f => f.exists); - setSections(groupFilesByDay(files)); - }, []); - - useFocusEffect( - useCallback(() => { - load(); - }, [load]) - ); - - const handlePickDirectory = async () => { - const folder = await pickDirectory(); - if ( !folder ) return; - const saved = await saveDirectory(folder); - try { - const result = await syncRoot(saved.resource_id); - console.info('walk', JSON.stringify(result)); - } catch (error) { - console.warn('walk failed', error); - } - await load(); - }; - - return ( - - - - - {i18n.t('add_folder')} - - item.key} - renderSectionHeader={({ section }) => ( - {section.dayLabel} - )} - renderItem={({ item }) => ( - - - {item.right ? : } - - )} - stickySectionHeadersEnabled - contentContainerStyle={styles.listContent} - ListEmptyComponent={ - - {i18n.t('no_files_yet')} - - } - /> - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - }, - button: { - backgroundColor: '#1a73e8', - paddingVertical: 12, - borderRadius: 8, - alignItems: 'center', - margin: 16, - }, - buttonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, - listContent: { - paddingHorizontal: 16, - paddingBottom: 110, - }, - row: { - flexDirection: 'row', - gap: 12, - marginBottom: 12, - }, - sectionHeader: { - fontSize: 14, - fontWeight: '600', - color: '#6b7280', - textTransform: 'uppercase', - marginBottom: 12, - backgroundColor: '#fff', - }, - card: { - flex: 1, - backgroundColor: '#f7f8fa', - borderRadius: 10, - padding: 12, - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#eaeaea', - }, - cardSpacer: { - flex: 1, - }, - cardTitle: { - fontSize: 14, - fontWeight: '500', - marginTop: 8, - }, - cardMeta: { - fontSize: 12, - color: '#888', - marginTop: 2, - }, - empty: { - color: '#888', - textAlign: 'center', - marginTop: 24, - }, -}); \ No newline at end of file diff --git a/mobile/app/login.tsx b/mobile/app/login.tsx deleted file mode 100644 index ba6f0f2..0000000 --- a/mobile/app/login.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { useRouter } from 'expo-router'; -import { useState } from 'react'; -import { - Alert, - KeyboardAvoidingView, - Platform, - Pressable, - ScrollView, - StyleSheet, - Text, - TextInput, - View, -} from 'react-native'; -import { useAuth } from '../context/AuthContext'; -import { ApiError } from '../api/client'; -import i18n from '../i18n'; - -export default function Login() { - const router = useRouter(); - const { signIn, user, continueWithoutAccount } = useAuth(); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(null); - - const handleSubmit = async () => { - if (!username.trim() || !password) { - setError(i18n.t('login_error_required')); - return; - } - setSubmitting(true); - setError(null); - - // Changer de compte en étant connecté remplace la session locale (garde-fou). - if (user) { - const confirmed = await new Promise((resolve) => { - Alert.alert(i18n.t('login_switch_title'), i18n.t('login_switch_message'), [ - { text: i18n.t('login_switch_cancel'), style: 'cancel', onPress: () => resolve(false) }, - { text: i18n.t('login_switch_confirm'), style: 'destructive', onPress: () => resolve(true) }, - ]); - }); - if (!confirmed) { - setSubmitting(false); - return; - } - } - - try { - await signIn(username.trim(), password); - router.replace('/'); - } catch (err) { - setError(err instanceof ApiError ? err.message : i18n.t('login_error_generic')); - } finally { - setSubmitting(false); - } - }; - - return ( - - - {i18n.t('login_subtitle')} - - - {error ? {error} : null} - - - {submitting ? i18n.t('login_submitting') : i18n.t('login_submit')} - - - { - continueWithoutAccount(); - router.replace('/'); - }} - > - {i18n.t('login_skip')} - - - - ); -} - -const styles = StyleSheet.create({ - flex: { - flex: 1, - }, - container: { - flexGrow: 1, - backgroundColor: '#fff', - padding: 24, - justifyContent: 'center', - gap: 12, - }, - title: { - fontSize: 18, - fontWeight: '600', - marginBottom: 8, - textAlign: 'center', - }, - input: { - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#ccc', - borderRadius: 8, - paddingHorizontal: 14, - paddingVertical: 12, - fontSize: 15, - }, - error: { - color: '#c5221f', - fontSize: 14, - }, - button: { - backgroundColor: '#1a73e8', - paddingVertical: 12, - borderRadius: 8, - alignItems: 'center', - marginTop: 4, - }, - buttonDisabled: { - opacity: 0.6, - }, - buttonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, - skipButton: { - alignItems: 'center', - paddingVertical: 10, - marginTop: 4, - }, - skipButtonText: { - color: '#1a73e8', - fontSize: 15, - fontWeight: '500', - }, -}); \ No newline at end of file diff --git a/mobile/app/search.tsx b/mobile/app/search.tsx deleted file mode 100644 index ffac505..0000000 --- a/mobile/app/search.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Stack } from 'expo-router'; -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, View } from 'react-native'; -import FloatingNavBar from '../components/FloatingNavBar'; -import i18n from '../i18n'; - -export default function Search() { - return ( - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - }, -}); \ No newline at end of file diff --git a/mobile/app/settings.tsx b/mobile/app/settings.tsx deleted file mode 100644 index 2e7b44d..0000000 --- a/mobile/app/settings.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Stack } from 'expo-router'; -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, View } from 'react-native'; -import FloatingNavBar from '../components/FloatingNavBar'; -import i18n from '../i18n'; - -export default function Settings() { - return ( - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - }, -}); \ No newline at end of file diff --git a/mobile/assets/android-icon-background.png b/mobile/assets/android-icon-background.png deleted file mode 100644 index 5ffefc5..0000000 Binary files a/mobile/assets/android-icon-background.png and /dev/null differ diff --git a/mobile/assets/android-icon-foreground.png b/mobile/assets/android-icon-foreground.png deleted file mode 100644 index 3a9e501..0000000 Binary files a/mobile/assets/android-icon-foreground.png and /dev/null differ diff --git a/mobile/assets/android-icon-monochrome.png b/mobile/assets/android-icon-monochrome.png deleted file mode 100644 index 77484eb..0000000 Binary files a/mobile/assets/android-icon-monochrome.png and /dev/null differ diff --git a/mobile/assets/favicon.png b/mobile/assets/favicon.png deleted file mode 100644 index 408bd74..0000000 Binary files a/mobile/assets/favicon.png and /dev/null differ diff --git a/mobile/assets/icon.png b/mobile/assets/icon.png deleted file mode 100644 index 7165a53..0000000 Binary files a/mobile/assets/icon.png and /dev/null differ diff --git a/mobile/assets/splash-icon.png b/mobile/assets/splash-icon.png deleted file mode 100644 index 03d6f6b..0000000 Binary files a/mobile/assets/splash-icon.png and /dev/null differ diff --git a/mobile/babel.config.js b/mobile/babel.config.js deleted file mode 100644 index 5f462d8..0000000 --- a/mobile/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - }; -}; \ No newline at end of file diff --git a/mobile/components/FloatingNavBar.tsx b/mobile/components/FloatingNavBar.tsx deleted file mode 100644 index 00692ef..0000000 --- a/mobile/components/FloatingNavBar.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import { View, Text, Pressable, StyleSheet, Platform } from 'react-native'; -import { useRouter, usePathname } from 'expo-router'; -import { Ionicons } from '@expo/vector-icons'; -import i18n from "../i18n" -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -type TabKey = 'files' | 'search' | 'settings'; - -interface TabItem { - key: TabKey; - labelKey: string; - route: string; - iconActive: keyof typeof Ionicons.glyphMap; - iconInactive: keyof typeof Ionicons.glyphMap; -} - -const TABS: TabItem[] = [ - { - key: 'files', - labelKey: 'nav.files', // "Fichiers" - route: '/', - iconActive: 'folder', - iconInactive: 'folder-outline', - }, - { - key: 'search', - labelKey: 'nav.search', // "Recherche" - route: '/search', - iconActive: 'search', - iconInactive: 'search-outline', - }, - { - key: 'settings', - labelKey: 'nav.settings', // "Configuration" - route: '/settings', - iconActive: 'settings', - iconInactive: 'settings-outline', - }, -]; - -export default function FloatingNavBar() { - const router = useRouter(); - const pathname = usePathname(); - const insets = useSafeAreaInsets(); - - return ( - - - {TABS.map((tab) => { - const isActive = pathname === tab.route; - - return ( - [ - styles.tab, - pressed && styles.tabPressed, - ]} - onPress={() => { - if (!isActive) { - router.push(tab.route as any); - } - }} - > - - - {i18n.t(tab.labelKey)} - - - ); - })} - - - ); -} - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - left: 20, - right: 20, - alignItems: 'center', - zIndex: 100, - }, - container: { - flexDirection: 'row', - backgroundColor: '#ffffff', - borderRadius: 30, - paddingVertical: 8, - paddingHorizontal: 12, - width: '100%', - maxWidth: 400, - justifyContent: 'space-around', - alignItems: 'center', - - // Effet d'ombre / Flottant - ...Platform.select({ - ios: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 6 }, - shadowOpacity: 0.12, - shadowRadius: 10, - }, - android: { - elevation: 8, - }, - }), - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#eaeaea', - }, - tab: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 4, - }, - tabPressed: { - opacity: 0.7, - }, - label: { - fontSize: 11, - marginTop: 3, - fontWeight: '500', - }, - labelActive: { - color: '#1a73e8', - fontWeight: '700', - }, - labelInactive: { - color: '#6b7280', - }, -}); \ No newline at end of file diff --git a/mobile/context/AuthContext.tsx b/mobile/context/AuthContext.tsx deleted file mode 100644 index 2673c11..0000000 --- a/mobile/context/AuthContext.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import type { ReactNode } from 'react'; -import { useSyncDevice } from '../features/syncDevice'; -import { api, setAuthToken, setUnauthorizedHandler } from '../api/client'; -import { - clearActiveUserId, - getDeviceUserId, - setActiveUserId, -} from '../services/localStorage'; -import { - clearStoredSession, - getStoredAccount, - getStoredToken, - setStoredSession, -} from '../services/secureStore'; -import type { AuthContextValue, AuthStatus, User } from './AuthContext.types'; - -const AuthContext = createContext(null); - -export function AuthProvider({ children }: { children: ReactNode }) { - const [user, setUser] = useState(null); - const [deviceUserId, setDeviceUserId] = useState(null); - const [status, setStatus] = useState('loading'); - - const signOut = async () => { - await Promise.allSettled([clearStoredSession(), clearActiveUserId()]); - setAuthToken(null); - setUser(null); - setStatus('signedOut'); - }; - - useEffect(() => { - let active = true; - - const bootstrap = async () => { - const id = await getDeviceUserId(); - if (active) setDeviceUserId(id); - - try { - // Enregistrement du device (idempotent côté serveur) — obligatoire - // avant le login (INVALID_DEVICE_ID sinon). Aucun token émis ici. - await api.registerDevice(id); - } catch (error) { - console.warn('device registration failed (offline?)', error); - } - - try { - const [token, storedUser] = await Promise.all([getStoredToken(), getStoredAccount()]); - if (active && token && storedUser) { - setAuthToken(token); - setActiveUserId(storedUser.id); - setUser(storedUser); - setStatus('signedIn'); - return; - } - } catch (error) { - console.warn('could not restore session', error); - } - if (active) setStatus('signedOut'); - }; - - bootstrap(); - // Boucle de sync en arrière-plan (décalée, annulée au unmount). - const stopSync = useSyncDevice(); - - return () => { - active = false; - stopSync(); - }; - }, []); - - // 401 sur un endpoint protégé (token expiré/révoqué, compte supprimé) → - // purge de la session. Le 401 du login est exonéré côté client (option - // skipUnauthorizedHandling dans api/client.ts). - useEffect(() => { - setUnauthorizedHandler(() => { - void signOut(); - }); - return () => setUnauthorizedHandler(null); - }, [signOut]); - - const signIn = async (username: string, password: string) => { - const id = deviceUserId ?? (await getDeviceUserId()); - const result = await api.login(username, password, id); - await setStoredSession(result.data.token, result.data.user); - await setActiveUserId(result.data.user.id); - setAuthToken(result.data.token); - setUser(result.data.user); - setStatus('signedIn'); - }; - - const continueWithoutAccount = () => { - setStatus('local'); - }; - - const value = useMemo( - () => ({ user, deviceUserId, status, signIn, signOut, continueWithoutAccount }), - [user, deviceUserId, status, signIn, signOut, continueWithoutAccount], - ); - - return {children}; -} - -export function useAuth(): AuthContextValue { - const ctx = useContext(AuthContext); - if (!ctx) { - throw new Error('useAuth must be used within an AuthProvider'); - } - return ctx; -} \ No newline at end of file diff --git a/mobile/context/AuthContext.types.ts b/mobile/context/AuthContext.types.ts deleted file mode 100644 index e9fb868..0000000 --- a/mobile/context/AuthContext.types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { User } from '../api/types'; - -export type { User }; - -export type AuthStatus = 'loading' | 'signedOut' | 'local' | 'signedIn'; - -export type AuthContextValue = { - user: User | null; - deviceUserId: string | null; - status: AuthStatus; - signIn: (username: string, password: string) => Promise; - signOut: () => Promise; - continueWithoutAccount: () => void; -}; \ No newline at end of file diff --git a/mobile/features/syncDevice.ts b/mobile/features/syncDevice.ts deleted file mode 100644 index 856c805..0000000 --- a/mobile/features/syncDevice.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { listEntries, listFoldersChunked, yieldToMainThread } from '../services/safWalk'; -import type { FileEntry } from '../services/safDirectory.types'; -import { - checkpointDatabase, - getFiles, - getFolder, - getFolders, - saveFile, - saveFolder, - getUserPreferences, - withTransaction, - type StoredFile, - type StoredFolder, -} from '../services/db'; -import type { SyncResult } from './syncDevice.types'; -import { pushPendingOps, refreshPermissions } from './syncOutbox'; - -function uriDepth(uri: string): number { - return uri.split('/').length; -} - -function dirname(uri: string): string { - return uri.slice(0, uri.lastIndexOf('/')); -} - -function storedToEntry(stored: StoredFile): FileEntry { - return { - uri: stored.uri ?? '', - name: stored.name, - isDirectory: false, - extension: stored.extension, - exists: false, - size: stored.size, - type: stored.type, - lastModified: stored.lastModified, - }; -} - -function isChildOf(uri: string, rootUri: string): boolean { - return uri === rootUri || uri.startsWith(rootUri + '/'); -} - -// Single-flight : toutes les marches SAF (`syncRoot` manuel et boucle de fond) -// sont sérialisées sur une chaîne module-level. Sans cela, deux `withTransaction` -// concurrents entrelacent leurs upserts et heurtent l'index partiel unique sur -// `uri` → `SQLITE_CONSTRAINT` → transaction annulée (FEEDBACK #8). -let chain: Promise = Promise.resolve(); - -function enqueue(work: () => Promise): Promise { - const p = chain.then(work, work); - chain = p.catch(() => {}); - return p; -} - -async function doSyncRoot(root: StoredFolder): Promise { - const rootUri = root.uri; - if (!rootUri) throw new Error(`root '${root.name}' has no physical uri`); - - // Phase 1 — listing SAF (I/O disque, HORS transaction) : la phase d'écriture - // ne doit verrouiller la base que pour les writes SQL purs, pas pendant tout - // le parcours (FEEDBACK #9 : l'UI lit la DB pendant un walk de plusieurs minutes). - const folders = ( - await listFoldersChunked(rootUri, { recursive: true, includeRoot: true }) - ).sort((a, b) => uriDepth(a.uri) - uriDepth(b.uri)); - - const fileEntries = new Map(); - let lastYield = Date.now(); - for (const folder of folders) { - if (!folder.uri) continue; - if (Date.now() - lastYield >= 16) { - lastYield = Date.now(); - await yieldToMainThread(); - } - fileEntries.set( - folder.uri, - listEntries(folder.uri).filter((entry) => !entry.isDirectory), - ); - } - - // Phase 2 — writes DB (transaction courte, SQL pur). - return withTransaction(async () => { - const seen = new Set(); - const resourceIdByUri = new Map(); - let files = 0; - - for (const folder of folders) { - seen.add(folder.uri); - const parentUri = dirname(folder.uri); - const parentResourceId = - folder.uri === rootUri ? null : (resourceIdByUri.get(parentUri) ?? root.resource_id); - const saved = await saveFolder( - { uri: folder.uri, name: folder.name, exists: folder.exists }, - { parentResourceId }, - ); - resourceIdByUri.set(folder.uri, saved.resource_id); - } - - for (const folder of folders) { - if (!folder.uri) continue; - const entries = fileEntries.get(folder.uri) ?? []; - for (const entry of entries) { - seen.add(entry.uri); - await saveFile(entry, resourceIdByUri.get(folder.uri)!); - files++; - } - } - - let missing = 0; - for (const folder of await getFolders()) { - if (folder.uri && isChildOf(folder.uri, rootUri) && folder.exists && !seen.has(folder.uri)) { - await saveFolder( - { uri: folder.uri, name: folder.name, exists: false, resource_id: folder.resource_id }, - { syncStatus: folder.syncStatus }, - ); - missing++; - } - } - for (const file of await getFiles()) { - if (file.uri && isChildOf(file.uri, rootUri) && file.exists && !seen.has(file.uri)) { - await saveFile(storedToEntry(file), file.folder_resource_id, { - resource_id: file.resource_id, - syncStatus: file.syncStatus, - }); - missing++; - } - } - - return { rootUri, folders: folders.length, files, missing }; - }); -} - -export async function syncRoot(rootResourceId: string): Promise { - const root = await getFolder(rootResourceId); - if (!root) throw new Error('unknown root folder'); - return enqueue(() => doSyncRoot(root)); -} - -export async function syncDevice(): Promise { - return enqueue(async () => { - const roots = (await getFolders()).filter( - (folder) => folder.parent_resource_id === null && folder.uri !== null, - ); - const results: SyncResult[] = []; - for (const root of roots) { - results.push(await doSyncRoot(root)); - } - await checkpointDatabase(); - return results; - }); -} - -export function useSyncDevice(intervalMs = 30_000): () => void { - let stopped = false; - - const tick = async () => { - try { - const preferences = await getUserPreferences(); - if (preferences.syncMode === 'none') return; - - const results = await syncDevice(); - console.info('syncDevice', JSON.stringify(results)); - // Puis pousser l'outbox (si un token est disponible) et rafraîchir le - // cache des permissions (delta). - const { pushed, retried } = await pushPendingOps(); - if (pushed > 0 || retried > 0) { - console.info('pushPendingOps', JSON.stringify({ pushed, retried })); - } - const perms = await refreshPermissions(); - if (perms > 0) { - console.info('refreshPermissions', perms); - } - } catch (error) { - console.warn('syncDevice failed, retrying later', error); - } - }; - - const loop = async () => { - while (!stopped) { - await new Promise((resolve) => setTimeout(resolve, intervalMs)); - if (stopped) return; - await tick(); - } - }; - - // Premier cycle différé : laisser le boot et l'écran actif répondre avant - // de lancer un walk SAF (potentiellement long) en arrière-plan. - const first = setTimeout(() => { - void loop(); - }, 2000); - - return () => { - stopped = true; - clearTimeout(first); - }; -} \ No newline at end of file diff --git a/mobile/features/syncDevice.types.ts b/mobile/features/syncDevice.types.ts deleted file mode 100644 index 07bdac7..0000000 --- a/mobile/features/syncDevice.types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type SyncResult = { - rootUri: string; - folders: number; - files: number; - missing: number; -}; \ No newline at end of file diff --git a/mobile/features/syncOutbox.ts b/mobile/features/syncOutbox.ts deleted file mode 100644 index 6e73a2c..0000000 --- a/mobile/features/syncOutbox.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { api, hasAuthToken, ApiError } from '../api/client'; -import type { SyncOperation } from '../api/types'; -import { - getActiveUserId, - listQueuedOperations, - markPendingOperation, - scheduleRetries, - saveResourcePermission, - MAX_PENDING_ATTEMPTS, -} from '../services/db'; -import type { PendingOperation } from '../services/db/types'; - -// Nombre max d'opérations par push (pour ne pas dépasser la taille du body). -const SYNC_BATCH_SIZE = 50; - -// Délai minimal avant de réessayer un batch complet après une erreur transitoire -// (réseau, proxy, 5xx). Aucun compteur d'attempts n'est incrémenté pour éviter -// les dead-letters prématurées. -const TRANSIENT_RETRY_MS = 15_000; - -function isTransientError(error: unknown): boolean { - if (!(error instanceof ApiError)) return true; - if (error.code === 'NETWORK_ERROR' || error.code.startsWith('HTTP_5')) return true; - if (error.code.startsWith('HTTP_4')) return false; - return true; -} - -function toSyncOperation(op: PendingOperation): SyncOperation { - return { - operation_id: op.id, - ref_type: op.refType, - ref_id: op.refId, - resource_id: op.resourceId ?? '', - resource_type: op.resourceType ?? 'file', - operation: op.operation, - payload: (typeof op.payload === 'object' && op.payload !== null ? op.payload : {}) as Record, - }; -} - -// ---- outbox push ----------------------------------------------------------- - -export type PushResult = { pushed: number; retried: number }; - -export async function pushPendingOps(): Promise { - if (!hasAuthToken()) return { pushed: 0, retried: 0 }; - - const queued = await listQueuedOperations(SYNC_BATCH_SIZE); - if (queued.length === 0) return { pushed: 0, retried: 0 }; - - let result; - try { - result = (await api.syncOps(queued.map(toSyncOperation))).data; - } catch (error) { - if (isTransientError(error)) { - const retryAt = Date.now() + TRANSIENT_RETRY_MS; - await scheduleRetries(queued.map((op) => op.id), retryAt); - return { pushed: 0, retried: queued.length }; - } - const firstOp = queued[0]; - await markPendingOperation( - firstOp.id, - 'failed', - error instanceof ApiError ? `${error.code}: ${error.message}` : String(error), - ); - return { pushed: 0, retried: 0 }; - } - - // `applied` = INDEX : les opérations [0, applied) sont confirmées côté serveur. - const applied = Math.min(result.applied, queued.length); - for (const op of queued.slice(0, applied)) { - await markPendingOperation(op.id, 'completed'); - } - - if (result.failed && applied < queued.length) { - const failed = queued[applied]; - await markPendingOperation(failed.id, 'failed', `${result.failed.code}: ${result.failed.message}`); - // Les opérations [applied+1, length) restent pending : le serveur ne les - // a pas reçues (il s'arrête à l'index) et elles seront resoumises au - // prochain tick. - } - - return { pushed: applied, retried: 0 }; -} - -// ---- permissions snapshot -------------------------------------------------- - -// Delta monotone PAR COMPTE (clé = active_user_id, NULL hors compte) : la -// valeur max de cached_at du dernier snapshot sert de borne `after` pour le -// prochain appel. Le snapshot n'est pas rejoué quand on change de compte. -const lastPermissionCachedAtByUser = new Map(); - -export async function refreshPermissions(): Promise { - if (!hasAuthToken()) return 0; - const activeUserId = await getActiveUserId(); - const after = lastPermissionCachedAtByUser.get(activeUserId) ?? undefined; - const perms = await api.getSyncPermissions(after); - if (perms.data.length === 0) return 0; - for (const p of perms.data) { - await saveResourcePermission(p); - } - lastPermissionCachedAtByUser.set(activeUserId, Math.max(...perms.data.map((p) => p.cachedAt))); - return perms.data.length; -} - -// Visible uniquement pour les tests unitaires (reset de l'état en mémoire). -export function resetPermissionCachedAtForTests(): void { - lastPermissionCachedAtByUser.clear(); -} \ No newline at end of file diff --git a/mobile/hooks/useFiles.ts b/mobile/hooks/useFiles.ts deleted file mode 100644 index 03162ba..0000000 --- a/mobile/hooks/useFiles.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { api } from '../api/client'; -import type { FileDto, ListFilesParams } from '../api/types'; - -export function useFiles(params?: ListFilesParams) { - return useQuery({ - queryKey: ['files', params], - queryFn: () => api.listFiles(params), - }); -} - -export function useFile(id: string) { - return useQuery({ - queryKey: ['files', id], - queryFn: () => api.getFile(id), - enabled: id.length > 0, - }); -} - -export function useFileTags(id: string) { - return useQuery({ - queryKey: ['files', id, 'tags'], - queryFn: async (): Promise => { - const { data } = await api.getFile(id); - return data.tags ?? []; - }, - enabled: id.length > 0, - }); -} - -export type { FileDto }; \ No newline at end of file diff --git a/mobile/hooks/useSearch.ts b/mobile/hooks/useSearch.ts deleted file mode 100644 index faad007..0000000 --- a/mobile/hooks/useSearch.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { api } from '../api/client'; -import type { FileDto } from '../api/types'; - -export function useSearch(q: string, page?: number, pageSize?: number) { - const trimmed = q.trim(); - return useQuery({ - queryKey: ['search', trimmed, page, pageSize], - queryFn: () => api.searchFiles(trimmed, { page, pageSize }), - enabled: trimmed.length > 0, - }); -} - -export type { FileDto }; \ No newline at end of file diff --git a/mobile/hooks/useUpload.ts b/mobile/hooks/useUpload.ts deleted file mode 100644 index d79c53e..0000000 --- a/mobile/hooks/useUpload.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { api } from '../api/client'; -import type { FileDto, OcrJob } from '../api/types'; - -export function useUpload() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (input: { - file: { uri: string; name: string; mimeType: string }; - folderId?: string | null; - }) => api.uploadFile(input.file, input.folderId), - onSuccess: (result) => { - queryClient.invalidateQueries({ queryKey: ['files'] }); - queryClient.invalidateQueries({ queryKey: ['search'] }); - return result; - }, - }); -} - -export function useOcrJob(jobId?: string | null) { - return useQuery({ - queryKey: ['ocr', jobId], - queryFn: () => api.getOcrJob(jobId!), - enabled: Boolean(jobId), - refetchInterval: (query) => - query.state.data && query.state.data.data.status === 'done' ? false : 3000, - }); -} - -export function useCreateOcrJob() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (fileId: string) => api.createOcrJob(fileId), - onSuccess: (result) => { - queryClient.setQueryData(['ocr', result.data.id], result); - }, - }); -} - -export type { FileDto, OcrJob }; \ No newline at end of file diff --git a/mobile/i18n/en.json b/mobile/i18n/en.json deleted file mode 100644 index 12b72ed..0000000 --- a/mobile/i18n/en.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "files": "Files", - "search": "Search", - "configuration": "Settings", - "nav": { - "files": "Files", - "search": "Search", - "settings": "Settings" - }, - "add_folder": "Add a folder", - "no_folders_yet": "No folders yet. Tap \"Add a folder\" to sync a folder.", - "no_files_yet": "No files yet. Tap \"Add a folder\" to sync your files.", - "folder": "Folder", - "empty_folder": "Empty folder — next syncDevice will refresh it.", - "bytes": "B", - "kilobytes": "KB", - "megabytes": "MB", - "login_title": "Sign in", - "login_subtitle": "Sign in to your account", - "login_username": "Username", - "login_password": "Password", - "login_submit": "Sign in", - "login_submitting": "Signing in…", - "login_error_required": "Username and password are required", - "login_error_generic": "Could not sign in right now", - "login_switch_title": "Account already signed in", - "login_switch_message": "This sign-in will replace the active account on this device. Continue?", - "login_switch_confirm": "Replace", - "login_switch_cancel": "Cancel", - "login_skip": "Continue without account", - "skip_message": "Local mode — your folders stay on this device and are not synced." -} diff --git a/mobile/i18n/fr.json b/mobile/i18n/fr.json deleted file mode 100644 index e18589e..0000000 --- a/mobile/i18n/fr.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "files": "Fichiers", - "search": "Recherche", - "configuration": "Configuration", - "nav": { - "files": "Fichiers", - "search": "Recherche", - "settings": "Réglages" - }, - "add_folder": "Ajouter un dossier", - "no_folders_yet": "Aucun dossier pour le moment. Appuie sur « Ajouter un dossier » pour synchroniser un dossier.", - "no_files_yet": "Aucun fichier pour le moment. Appuie sur « Ajouter un dossier » pour synchroniser tes fichiers.", - "folder": "Dossier", - "empty_folder": "Dossier vide — le prochain syncDevice l'actualisera.", - "bytes": "o", - "kilobytes": "Ko", - "megabytes": "Mo", - "login_title": "Connexion", - "login_subtitle": "Connecte-toi à ton compte", - "login_username": "Nom d'utilisateur", - "login_password": "Mot de passe", - "login_submit": "Se connecter", - "login_submitting": "Connexion…", - "login_error_required": "Nom d'utilisateur et mot de passe requis", - "login_error_generic": "Impossible de se connecter pour le moment", - "login_switch_title": "Compte déjà connecté", - "login_switch_message": "Cette connexion remplacera le compte actif sur cet appareil. Continuer ?", - "login_switch_confirm": "Remplacer", - "login_switch_cancel": "Annuler", - "login_skip": "Continuer sans compte", - "skip_message": "Mode local — tes dossiers restent sur cet appareil et ne sont pas synchronisés." -} diff --git a/mobile/i18n/index.ts b/mobile/i18n/index.ts deleted file mode 100644 index 1e6ffc7..0000000 --- a/mobile/i18n/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { I18n } from 'i18n-js'; -import { getLocales } from 'expo-localization'; -import fr from './fr.json'; -import en from './en.json'; - -const i18n = new I18n({ fr, en }); - -i18n.defaultLocale = 'fr'; -i18n.enableFallback = true; -i18n.locale = getLocales()[0]?.languageCode ?? 'fr'; - -export default i18n; diff --git a/mobile/metro.config.js b/mobile/metro.config.js deleted file mode 100644 index 07c9fce..0000000 --- a/mobile/metro.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require('expo/metro-config'); - -/** @type {import('expo/metro-config').MetroConfig} */ -const config = getDefaultConfig(__dirname); - -module.exports = config; diff --git a/mobile/package-lock.json b/mobile/package-lock.json deleted file mode 100644 index 4a1fb13..0000000 --- a/mobile/package-lock.json +++ /dev/null @@ -1,8186 +0,0 @@ -{ - "name": "webui", - "version": "2.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "webui", - "version": "2.0.0", - "dependencies": { - "@expo/vector-icons": "^15.1.1", - "@tanstack/react-query": "^5.102.8", - "expo": "~57.0.8", - "expo-constants": "~57.0.17", - "expo-file-system": "~57.0.6", - "expo-linking": "~57.0.9", - "expo-localization": "~57.0.1", - "expo-router": "~57.0.20", - "expo-secure-store": "~57.0.3", - "expo-sqlite": "~57.0.2", - "expo-status-bar": "~57.0.1", - "i18n-js": "^4.5.3", - "react": "19.2.3", - "react-dom": "19.2.3", - "react-native": "0.86.0", - "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "~4.26.0" - }, - "devDependencies": { - "@types/better-sqlite3": "^9.6.0", - "@types/react": "~19.2.2", - "babel-preset-expo": "^57.0.4", - "better-sqlite3": "^13.0.3", - "tsx": "^4.23.13", - "typescript": "~6.0.3" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", - "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", - "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-wrap-function": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", - "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", - "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-decorators": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", - "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", - "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", - "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", - "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", - "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", - "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", - "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", - "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", - "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", - "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", - "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", - "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", - "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", - "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-flow": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", - "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", - "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", - "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", - "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7", - "@babel/plugin-transform-parameters": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", - "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", - "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", - "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", - "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", - "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", - "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", - "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", - "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", - "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", - "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", - "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", - "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", - "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-syntax-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", - "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", - "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/plugin-transform-modules-commonjs": "^7.29.7", - "@babel/plugin-transform-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@expo-google-fonts/material-symbols": { - "version": "0.4.46", - "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.46.tgz", - "integrity": "sha512-iUs7THR195BR5MT8n07cOVSEjAGKqAT5pUX07Li4Am8qYBJBNAJh49ejTTB3cSyvpKWl9+mwrBqKkirzlBtZmQ==", - "license": "MIT AND Apache-2.0" - }, - "node_modules/@expo/code-signing-certificates": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", - "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", - "license": "MIT", - "dependencies": { - "node-forge": "^1.3.3" - } - }, - "node_modules/@expo/config": { - "version": "57.0.9", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.9.tgz", - "integrity": "sha512-dmzlKraIFxa7wLwV6K7WzI8jp6QZpW6Mc5mGjLimJUFjzh4uQdYaT3m3plEutM5yxBoBEwqzks7l+I/ljCbxAQ==", - "license": "MIT", - "dependencies": { - "@expo/config-plugins": "~57.0.9", - "@expo/config-types": "^57.0.2", - "@expo/json-file": "^11.0.1", - "@expo/require-utils": "^57.0.5", - "deepmerge": "^4.3.1", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "resolve-workspace-root": "^2.0.0", - "semver": "^7.6.0", - "slugify": "^1.3.4" - } - }, - "node_modules/@expo/config-plugins": { - "version": "57.0.9", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-57.0.9.tgz", - "integrity": "sha512-hHgfL1avkCdEvDSw7IwlKwRYYNgcxzbNNMIk6W6lTkJpY0MajinAfeJUS0J+wPCsjUfGbVqOJM+XhPaO5ulUxg==", - "license": "MIT", - "dependencies": { - "@expo/config-types": "^57.0.2", - "@expo/json-file": "~11.0.1", - "@expo/plist": "^0.8.1", - "@expo/require-utils": "^57.0.5", - "@expo/sdk-runtime-versions": "^1.0.0", - "chalk": "^4.1.2", - "debug": "^4.3.5", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "semver": "^7.5.4", - "slugify": "^1.6.6", - "xcode": "^3.0.1", - "xml2js": "0.6.0" - } - }, - "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/config-types": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-57.0.2.tgz", - "integrity": "sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==", - "license": "MIT" - }, - "node_modules/@expo/config/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/devcert": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz", - "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==", - "license": "MIT", - "dependencies": { - "@expo/sudo-prompt": "^9.3.1", - "debug": "^3.1.0" - } - }, - "node_modules/@expo/devcert/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@expo/devtools": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-57.0.1.tgz", - "integrity": "sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@expo/dom-webview": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-57.0.1.tgz", - "integrity": "sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/@expo/env": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.3.tgz", - "integrity": "sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==", - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "debug": "^4.3.4", - "getenv": "^2.0.0" - }, - "engines": { - "node": ">=20.12.0" - } - }, - "node_modules/@expo/expo-modules-macros-plugin": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@expo/expo-modules-macros-plugin/-/expo-modules-macros-plugin-0.6.1.tgz", - "integrity": "sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==", - "license": "MIT" - }, - "node_modules/@expo/fingerprint": { - "version": "0.20.12", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.12.tgz", - "integrity": "sha512-FIR5fkZYeFaLSowmjgyB6RPKl8AXeE8HuCBHHvyxK8UhOjpPfCAmzT4E7v2yub4qqDafKnfcOFCYxvpUEu+01w==", - "license": "MIT", - "dependencies": { - "@expo/env": "^2.4.3", - "@expo/spawn-async": "^1.8.0", - "arg": "^5.0.2", - "chalk": "^4.1.2", - "debug": "^4.3.4", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "ignore": "^5.3.1", - "minimatch": "^10.2.2", - "resolve-from": "^5.0.0", - "semver": "^7.6.0" - }, - "bin": { - "fingerprint": "bin/cli.js" - } - }, - "node_modules/@expo/fingerprint/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/image-utils": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.11.5.tgz", - "integrity": "sha512-KPQBTpmpAfy/Vu9y4wPW808/qtZxjYmyJg8cm2QCPAupp+qEWA3b5zmk0ulOwQ9OgeHxuCPgUqWgkwHFo7UsrQ==", - "license": "MIT", - "dependencies": { - "@expo/require-utils": "^57.0.5", - "@expo/spawn-async": "^1.8.0", - "chalk": "^4.0.0", - "getenv": "^2.0.0", - "jimp-compact": "0.16.1", - "parse-png": "^2.1.0", - "semver": "^7.6.0" - } - }, - "node_modules/@expo/image-utils/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/inline-modules": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.7.tgz", - "integrity": "sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==", - "license": "MIT", - "dependencies": { - "@expo/config-plugins": "~57.0.9" - } - }, - "node_modules/@expo/json-file": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-11.0.1.tgz", - "integrity": "sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.20.0", - "json5": "^2.2.3" - } - }, - "node_modules/@expo/local-build-cache-provider": { - "version": "57.0.8", - "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.8.tgz", - "integrity": "sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==", - "license": "MIT", - "dependencies": { - "@expo/config": "~57.0.9", - "chalk": "^4.1.2" - } - }, - "node_modules/@expo/log-box": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-57.0.4.tgz", - "integrity": "sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==", - "license": "MIT", - "dependencies": { - "@expo/dom-webview": "^57.0.1", - "anser": "^1.4.9", - "stacktrace-parser": "^0.1.10" - }, - "peerDependencies": { - "@expo/dom-webview": "^57.0.1", - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/@expo/metro": { - "version": "56.0.2", - "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-56.0.2.tgz", - "integrity": "sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==", - "license": "MIT", - "dependencies": { - "metro": "0.84.5", - "metro-babel-transformer": "0.84.5", - "metro-cache": "0.84.5", - "metro-cache-key": "0.84.5", - "metro-config": "0.84.5", - "metro-core": "0.84.5", - "metro-file-map": "0.84.5", - "metro-minify-terser": "0.84.5", - "metro-resolver": "0.84.5", - "metro-runtime": "0.84.5", - "metro-source-map": "0.84.5", - "metro-symbolicate": "0.84.5", - "metro-transform-plugins": "0.84.5", - "metro-transform-worker": "0.84.5" - } - }, - "node_modules/@expo/metro-file-map": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/metro-file-map/-/metro-file-map-57.0.3.tgz", - "integrity": "sha512-1OXy+uPYY5uc7Tm4VBsd2NRn+3wHhqeqNuEO/Xo4kmYgv8FjYgUAc+bUXON9FpC2ikcLn4EVlGM9ce2exx9Mlg==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "fb-watchman": "^2.0.2", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "node_modules/@expo/osascript": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.7.1.tgz", - "integrity": "sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==", - "license": "MIT", - "dependencies": { - "@expo/spawn-async": "^1.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@expo/package-manager": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.13.1.tgz", - "integrity": "sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==", - "license": "MIT", - "dependencies": { - "@expo/json-file": "^11.0.1", - "@expo/spawn-async": "^1.8.0", - "chalk": "^4.0.0", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "resolve-workspace-root": "^2.0.0" - } - }, - "node_modules/@expo/plist": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.8.1.tgz", - "integrity": "sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==", - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.8.8", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - } - }, - "node_modules/@expo/prebuild-config": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.15.tgz", - "integrity": "sha512-xTbWHroj0PDmlbqvmU+zF9ZZxveJkiuyiPoeRJYRGruFHebRAWnoTdw5S7d/UCzDBI8ropGGu9g2eb2nMxtvAw==", - "license": "MIT", - "dependencies": { - "@expo/config": "~57.0.9", - "@expo/config-plugins": "~57.0.9", - "@expo/config-types": "^57.0.2", - "@expo/image-utils": "^0.11.5", - "@expo/json-file": "^11.0.1", - "@react-native/normalize-colors": "0.86.3", - "debug": "^4.3.1", - "expo-modules-autolinking": "~57.0.12", - "resolve-from": "^5.0.0", - "semver": "^7.6.0" - } - }, - "node_modules/@expo/prebuild-config/node_modules/@react-native/normalize-colors": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.3.tgz", - "integrity": "sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==", - "license": "MIT" - }, - "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/require-utils": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-57.0.5.tgz", - "integrity": "sha512-kTAXj9lDFEIPMsbAOGCGbjBbMF0oi7CqkYM79KOX0DDD9wSwXmlKL1z2h8OwsrBf7mbOo2DjlRvZu4BEjrIxGw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.20.0", - "@babel/core": "^7.25.2", - "@babel/plugin-transform-modules-commonjs": "^7.24.8" - }, - "peerDependencies": { - "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@expo/schema-utils": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-57.0.2.tgz", - "integrity": "sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==", - "license": "MIT" - }, - "node_modules/@expo/sdk-runtime-versions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", - "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==", - "license": "MIT" - }, - "node_modules/@expo/spawn-async": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.8.0.tgz", - "integrity": "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.6" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@expo/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", - "license": "MIT" - }, - "node_modules/@expo/vector-icons": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", - "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", - "license": "MIT", - "peerDependencies": { - "expo-font": ">=14.0.4", - "react": "*", - "react-native": "*" - } - }, - "node_modules/@expo/xcpretty": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.5.tgz", - "integrity": "sha512-J3eL4n4h5QTwfD0SIz8OIk6/+sOL/hFZAMacgCM07UNlxBQfJipEpIC2AQxvGkbYeStByJ0TVhQAJo+DeNgaSQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/code-frame": "^7.20.0", - "chalk": "^4.1.0", - "js-yaml": "^4.1.0" - }, - "bin": { - "excpretty": "build/cli.js" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", - "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", - "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-slot": "1.3.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", - "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", - "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", - "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-dismissable-layer": "1.1.19", - "@radix-ui/react-focus-guards": "1.1.6", - "@radix-ui/react-focus-scope": "1.1.16", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-portal": "1.1.17", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-slot": "1.3.3", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-layout-effect": "1.1.4", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.7.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", - "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", - "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-effect-event": "0.0.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", - "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", - "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", - "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", - "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", - "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", - "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.3.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", - "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-collection": "1.1.15", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-controllable-state": "1.2.6", - "@radix-ui/react-use-is-hydrated": "0.1.3", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", - "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.5" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", - "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", - "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-use-effect-event": "0.0.5", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", - "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", - "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", - "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@react-native-masked-view/masked-view": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz", - "integrity": "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=16", - "react-native": ">=0.57" - } - }, - "node_modules/@react-native/assets-registry": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", - "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", - "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.3.tgz", - "integrity": "sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.86.3" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.0.tgz", - "integrity": "sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.86.0", - "babel-plugin-syntax-hermes-parser": "0.36.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/babel-preset/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.0.tgz", - "integrity": "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.86.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/babel-preset/node_modules/@react-native/codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", - "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/babel-preset/node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", - "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-parser": "0.36.0" - } - }, - "node_modules/@react-native/babel-preset/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.3.tgz", - "integrity": "sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", - "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", - "license": "MIT", - "dependencies": { - "@react-native/dev-middleware": "0.86.0", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "metro": "^0.84.3", - "metro-config": "^0.84.3", - "metro-core": "^0.84.3", - "semver": "^7.1.3" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@react-native-community/cli": "*", - "@react-native/metro-config": "0.86.0" - }, - "peerDependenciesMeta": { - "@react-native-community/cli": { - "optional": true - }, - "@react-native/metro-config": { - "optional": true - } - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", - "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", - "license": "BSD-3-Clause", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/debugger-shell": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", - "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/dev-middleware": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", - "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.86.0", - "@react-native/debugger-shell": "0.86.0", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.3.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", - "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", - "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/js-polyfills": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", - "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", - "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.0.tgz", - "integrity": "sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.86.0", - "hermes-parser": "0.36.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/metro-config": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.0.tgz", - "integrity": "sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@react-native/js-polyfills": "0.86.0", - "@react-native/metro-babel-transformer": "0.86.0", - "metro-config": "^0.84.3", - "metro-runtime": "^0.84.3" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/normalize-colors": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", - "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", - "license": "MIT" - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", - "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", - "license": "MIT", - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@types/react": "^19.2.0", - "react": "*", - "react-native": "0.86.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.12", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", - "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", - "license": "MIT" - }, - "node_modules/@tanstack/query-core": { - "version": "5.102.8", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", - "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.102.8", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", - "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.102.8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@types/better-sqlite3": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-9.6.0.tgz", - "integrity": "sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.0.tgz", - "integrity": "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==", - "license": "MIT", - "dependencies": { - "undici-types": "~8.9.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-test-renderer": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", - "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", - "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", - "license": "ISC" - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", - "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/agent-cli-detector": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/agent-cli-detector/-/agent-cli-detector-0.1.7.tgz", - "integrity": "sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==", - "license": "MIT", - "bin": { - "agent-cli-detector": "dist/cli.js" - }, - "engines": { - "node": ">=18.18" - } - }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/await-lock": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", - "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", - "license": "MIT" - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-react-compiler": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.0" - } - }, - "node_modules/babel-plugin-react-native-web": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", - "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", - "license": "MIT" - }, - "node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.1.tgz", - "integrity": "sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==", - "license": "MIT", - "dependencies": { - "hermes-parser": "0.36.1" - } - }, - "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", - "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", - "license": "MIT" - }, - "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", - "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.36.1" - } - }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" - } - }, - "node_modules/babel-preset-expo": { - "version": "57.0.11", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.11.tgz", - "integrity": "sha512-R0NouDI3nzQUsjBh5TUJSDXLmuVzE2bp2YE0ywJkKxzrdWVjShpAlZMBuDeOQy6iKGB6ZCCvMqbQvpqgNMJ7iw==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.20.5", - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.28.6", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-plugin-codegen": "0.86.3", - "babel-plugin-react-compiler": "^1.0.0", - "babel-plugin-react-native-web": "~0.21.0", - "babel-plugin-syntax-hermes-parser": "^0.36.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4" - }, - "peerDependencies": { - "@babel/runtime": "^7.20.0", - "expo": "*", - "expo-widgets": "^57.0.18", - "react-refresh": ">=0.14.0 <1.0.0" - }, - "peerDependenciesMeta": { - "@babel/runtime": { - "optional": true - }, - "expo": { - "optional": true - }, - "expo-widgets": { - "optional": true - } - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.21", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", - "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/better-sqlite3": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", - "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.0.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/bignumber.js": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.5.tgz", - "integrity": "sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw==", - "license": "MIT" - }, - "node_modules/bplist-creator": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", - "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", - "license": "MIT", - "dependencies": { - "stream-buffers": "2.2.x" - } - }, - "node_modules/bplist-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", - "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", - "license": "MIT", - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", - "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.20", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chromium-edge-launcher": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", - "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/core-js-compat": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", - "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.7" - }, - "engines": { - "node": ">=6.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/dnssd-advertise": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.6.tgz", - "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.425", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.425.tgz", - "integrity": "sha512-QvPtl41EUOnuT1HBvMKgxXRIaHNcagBPs50u7VULzhZXaGfqTbZyE16LQsctZ/RQHlGu+FOWeDTR4mY6YbeF1g==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "license": "MIT", - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/expo": { - "version": "57.0.21", - "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.21.tgz", - "integrity": "sha512-lQmC0kCJCleO+uLUwHXY0pLDzcvedKEsX+pmJp4mSxn3JlWDTZvUb0e5UIlV7+bqlN68LpAeKG4c2lrN4zuP0Q==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.0", - "@expo/cli": "^57.0.23", - "@expo/config": "~57.0.9", - "@expo/config-plugins": "~57.0.9", - "@expo/devtools": "~57.0.1", - "@expo/dom-webview": "~57.0.1", - "@expo/fingerprint": "^0.20.12", - "@expo/local-build-cache-provider": "^57.0.8", - "@expo/log-box": "^57.0.4", - "@expo/metro": "~56.0.2", - "@expo/metro-config": "~57.0.12", - "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~57.0.11", - "expo-asset": "~57.0.16", - "expo-constants": "~57.0.17", - "expo-file-system": "~57.0.6", - "expo-font": "~57.0.3", - "expo-keep-awake": "~57.0.1", - "expo-modules-autolinking": "~57.0.12", - "expo-modules-core": "~57.0.17", - "pretty-format": "^29.7.0", - "react-refresh": "^0.14.2", - "whatwg-url-minimum": "^0.1.2" - }, - "bin": { - "expo": "bin/cli", - "expo-modules-autolinking": "bin/autolinking", - "fingerprint": "bin/fingerprint" - }, - "peerDependencies": { - "@expo/dom-webview": "*", - "@expo/metro-runtime": "*", - "react": "*", - "react-dom": "*", - "react-native": "*", - "react-native-web": "*", - "react-native-webview": "*" - }, - "peerDependenciesMeta": { - "@expo/dom-webview": { - "optional": true - }, - "@expo/metro-runtime": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native-web": { - "optional": true - }, - "react-native-webview": { - "optional": true - } - } - }, - "node_modules/expo-constants": { - "version": "57.0.17", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.17.tgz", - "integrity": "sha512-cPWYBKN1SEbg2lXg2f8VkePJqGZrJPLvVdQDCTfbFu9sQHO1M31Y1zILReUuGnmt/VKeUz40ze579vR00xGu/A==", - "license": "MIT", - "dependencies": { - "@expo/env": "~2.4.3" - }, - "peerDependencies": { - "expo": "*", - "react-native": "*" - } - }, - "node_modules/expo-file-system": { - "version": "57.0.6", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.6.tgz", - "integrity": "sha512-pm8PMYEW6BnVOCBJ7df9FcDmQtE1tqImuYphlfYe1ipQRLYtdCayRczJcbKIsnB3mqEyp4gC2gWmMbsAsAOjVg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react-native": "*" - } - }, - "node_modules/expo-font": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.3.tgz", - "integrity": "sha512-kiVUnc2A8vAvO2FfDJTsQa5BwmY+PAkof/1wRb5MOkcX1jtiaSTwz9gCUAyscMBFLundZDmZrLy1P7LZVC+NvA==", - "license": "MIT", - "dependencies": { - "fontfaceobserver": "^2.1.0" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-glass-effect": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-glass-effect/-/expo-glass-effect-57.0.2.tgz", - "integrity": "sha512-k2Dk0uCJrGYe8DWfvlMPmJ6w7uxCmJpoJckIcNDoUn27g75c5Kv3nUa88+T4+CpeVkixwY7iWrv46B5De20S9Q==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-linking": { - "version": "57.0.9", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.9.tgz", - "integrity": "sha512-TQJe3NsnpZXApBg1n6DKaZ4TNhYm+iYWq7W9rTp+G44IBMZSUcI8ba+ffYC/LMY1qw+95AuBPIF1zTx1NQoTXw==", - "license": "MIT", - "dependencies": { - "expo-constants": "~57.0.17", - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-localization": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-57.0.1.tgz", - "integrity": "sha512-8Ffl4UTbOsQeGT0v5fxMbyPHyPMPnhSPDFQJa8p9rjJrthFoAtNi+fL6Ssmrvf1/7dmPq1mVY52MEt0TMEfgjA==", - "license": "MIT", - "dependencies": { - "rtl-detect": "^1.0.2" - }, - "peerDependencies": { - "expo": "*", - "react": "*" - } - }, - "node_modules/expo-modules-autolinking": { - "version": "57.0.12", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.12.tgz", - "integrity": "sha512-Q8KAlq37nLKsQ+HsS9NpQVpd5jCgqtu694TDUNHBUBpV9ViD82mRBh8Uug/h68RG9xnLS+kuL4nYaCuFRghHjg==", - "license": "MIT", - "dependencies": { - "@expo/require-utils": "^57.0.5", - "@expo/spawn-async": "^1.8.0", - "chalk": "^4.1.0", - "commander": "^7.2.0" - }, - "bin": { - "expo-modules-autolinking": "bin/expo-modules-autolinking.js" - } - }, - "node_modules/expo-modules-core": { - "version": "57.0.17", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.17.tgz", - "integrity": "sha512-hHJwGHW0sMQiOLzEEl1QbJeWkBEBgjlPjrdBVGWlzvgUbCXJfFbDU2nhxoyzLMZnvHkpIzpiJjj5cM5W8gbudA==", - "license": "MIT", - "dependencies": { - "@expo/expo-modules-macros-plugin": "0.6.1", - "expo-modules-jsi": "~57.1.0", - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" - }, - "peerDependenciesMeta": { - "react-native-worklets": { - "optional": true - } - } - }, - "node_modules/expo-modules-jsi": { - "version": "57.1.0", - "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.1.0.tgz", - "integrity": "sha512-a5ckeHfnbYfonhcHGkM0EU/a0Keh+/OufXy/HyFS+spr5Ib0N4Oh1ZsSYFQhp5xdOUFkVofKRhfH+VmcccIIpA==", - "license": "MIT", - "peerDependencies": { - "react-native": "*" - } - }, - "node_modules/expo-router": { - "version": "57.0.20", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.20.tgz", - "integrity": "sha512-pBY0Ek5opUgpUSdoX3bAmyd8mkGSeTIOEdIDdpSG/02owrrwpnaQuSn/XuwOtizoeFedAD3VgS5DDVSyQTdI7g==", - "license": "MIT", - "dependencies": { - "@expo/log-box": "^57.0.4", - "@expo/metro-runtime": "^57.0.15", - "@expo/schema-utils": "^57.0.2", - "@expo/ui": "^57.0.17", - "@radix-ui/react-slot": "^1.2.0", - "@radix-ui/react-tabs": "^1.1.12", - "@react-native-masked-view/masked-view": "^0.3.2", - "client-only": "^0.0.1", - "color": "^4.2.3", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "expo-glass-effect": "^57.0.2", - "expo-server": "^57.0.3", - "expo-symbols": "^57.0.2", - "fast-deep-equal": "^3.1.3", - "invariant": "^2.2.4", - "nanoid": "^3.3.8", - "query-string": "^7.1.3", - "react-fast-compare": "^3.2.2", - "react-is": "^19.1.0", - "react-native-drawer-layout": "^4.2.2", - "react-native-screens": "^4.26.0", - "server-only": "^0.0.1", - "sf-symbols-typescript": "^2.1.0", - "shallowequal": "^1.1.0", - "standard-navigation": "^0.0.5", - "vaul": "^1.1.2" - }, - "peerDependencies": { - "@expo/log-box": "^57.0.4", - "@expo/metro-runtime": "^57.0.15", - "@testing-library/react-native": ">= 13.2.0", - "expo": "*", - "expo-constants": "^57.0.17", - "expo-linking": "^57.0.9", - "react": "*", - "react-dom": "*", - "react-native": "*", - "react-native-gesture-handler": "*", - "react-native-reanimated": "*", - "react-native-safe-area-context": ">= 5.4.0", - "react-native-screens": "^4.26.0", - "react-native-web": "*", - "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" - }, - "peerDependenciesMeta": { - "@testing-library/react-native": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native-gesture-handler": { - "optional": true - }, - "react-native-reanimated": { - "optional": true - }, - "react-native-web": { - "optional": true - }, - "react-server-dom-webpack": { - "optional": true - } - } - }, - "node_modules/expo-router/node_modules/@expo/metro-runtime": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-57.0.15.tgz", - "integrity": "sha512-9RaFkqIzRud6ol9fc5ZKHgNDYeDjQVBdAQC+rjSQ6RiKV0pY+SjDqGmKY5a4M9TaV0qLMCNNrp5R88BqC2xbuQ==", - "license": "MIT", - "dependencies": { - "@expo/log-box": "^57.0.4", - "anser": "^1.4.9", - "pretty-format": "^29.7.0", - "stacktrace-parser": "^0.1.10", - "whatwg-fetch": "^3.0.0" - }, - "peerDependencies": { - "@expo/log-box": "^57.0.4", - "expo": "*", - "react": "*", - "react-dom": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/expo-router/node_modules/@expo/ui": { - "version": "57.0.17", - "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-57.0.17.tgz", - "integrity": "sha512-cnenUTsfX78i/EfXhA81Zv48sOxRdIq4YWCggwgBiPoOyS8J5CP/6dGyKo26ZVtcIsogWR4aXywaripeRF+6Ng==", - "license": "MIT", - "dependencies": { - "sf-symbols-typescript": "^2.1.0", - "vaul": "^1.1.2" - }, - "peerDependencies": { - "@babel/core": "*", - "expo": "*", - "react": "*", - "react-dom": "*", - "react-native": "*", - "react-native-worklets": "*" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native-worklets": { - "optional": true - } - } - }, - "node_modules/expo-router/node_modules/@radix-ui/react-tabs": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", - "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-id": "1.1.4", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-roving-focus": "1.1.19", - "@radix-ui/react-use-controllable-state": "1.2.6" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/expo-router/node_modules/react-is": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", - "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", - "license": "MIT" - }, - "node_modules/expo-secure-store": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.3.tgz", - "integrity": "sha512-w7XkSQeUiYXPoKXo1jSrQqql7pyCSyIzOp2k0apsZZBE+RkoLTQIMjcbqc181y6KgupfNnYxkaKe+4MEg94+SA==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-server": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.3.tgz", - "integrity": "sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==", - "license": "MIT", - "engines": { - "node": ">=20.16.0" - } - }, - "node_modules/expo-sqlite": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-57.0.2.tgz", - "integrity": "sha512-5KVbT7BQFZlIcQBXKWvwBERK3ODF6PSqct27GQKukZzAjsx2B97R+mwtYqrTUtUbw71VJrQrdr9Yl2OWYC7UpA==", - "license": "MIT", - "dependencies": { - "await-lock": "^2.2.2" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-status-bar": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-57.0.1.tgz", - "integrity": "sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-symbols": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-57.0.2.tgz", - "integrity": "sha512-qZ0iqOflm5lZGwRsQ5Y8sDksw3GAUKwHSX1bJBoocXf7gu14vafIXXYWte+JT9VfXAUGyKodJhLHP/GoOrcNWg==", - "license": "MIT", - "dependencies": { - "@expo-google-fonts/material-symbols": "^0.4.1", - "sf-symbols-typescript": "^2.0.0" - }, - "peerDependencies": { - "expo": "*", - "expo-font": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo/node_modules/@expo/cli": { - "version": "57.0.23", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.23.tgz", - "integrity": "sha512-stzSYxVwbWGbKR+mrYT6s4Rt6HrcUxb0JrHyebDGL4ehpvfRTv0rMzn4Q7fqB6f5wJ1KCmf6OAgqgK+TTB8/FA==", - "license": "MIT", - "dependencies": { - "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~57.0.9", - "@expo/config-plugins": "~57.0.9", - "@expo/devcert": "^1.2.1", - "@expo/env": "~2.4.3", - "@expo/image-utils": "^0.11.5", - "@expo/inline-modules": "^0.1.7", - "@expo/json-file": "^11.0.1", - "@expo/log-box": "^57.0.4", - "@expo/metro": "~56.0.2", - "@expo/metro-config": "~57.0.12", - "@expo/metro-file-map": "^57.0.3", - "@expo/osascript": "^2.7.1", - "@expo/package-manager": "^1.13.1", - "@expo/plist": "^0.8.1", - "@expo/prebuild-config": "^57.0.15", - "@expo/require-utils": "^57.0.5", - "@expo/router-server": "^57.0.9", - "@expo/schema-utils": "^57.0.2", - "@expo/spawn-async": "^1.8.0", - "@expo/ws-tunnel": "^2.0.0", - "@expo/xcpretty": "^4.4.4", - "@react-native/dev-middleware": "0.86.3", - "accepts": "^1.3.8", - "agent-cli-detector": "0.1.7", - "arg": "^5.0.2", - "bplist-creator": "0.1.0", - "bplist-parser": "^0.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "compression": "^1.7.4", - "connect": "^3.7.0", - "debug": "^4.3.4", - "dnssd-advertise": "^1.1.4", - "expo-server": "^57.0.3", - "fetch-nodeshim": "^0.4.10", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "lan-network": "^0.2.1", - "multitars": "^1.0.2", - "node-forge": "^1.3.3", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "picomatch": "^4.0.4", - "pretty-format": "^29.7.0", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "resolve-from": "^5.0.0", - "sandbox-cli-detector": "^0.2.0", - "semver": "^7.6.0", - "send": "^0.19.0", - "slugify": "^1.3.4", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "terminal-link": "^2.1.1", - "toqr": "^0.1.1", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1", - "zod": "^3.25.76" - }, - "bin": { - "expo-internal": "main.js" - }, - "peerDependencies": { - "expo": "*", - "expo-router": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "expo-router": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { - "version": "57.0.9", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.9.tgz", - "integrity": "sha512-/PxRQozFesIyCJZOAtrQE8XcmcojNiL5ctPMQnbE4ojC2EJPu0zc7c0Y4PuXsoRxrZxm8Usw76/lCVrXcfTZ2w==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "@expo/metro-runtime": "^57.0.15", - "expo": "*", - "expo-constants": "^57.0.17", - "expo-font": "^57.0.3", - "expo-router": "*", - "expo-server": "^57.0.3", - "react": "*", - "react-dom": "*", - "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" - }, - "peerDependenciesMeta": { - "@expo/metro-runtime": { - "optional": true - }, - "expo-router": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-server-dom-webpack": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/@expo/metro-config": { - "version": "57.0.12", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.12.tgz", - "integrity": "sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.20.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.5", - "@expo/config": "~57.0.9", - "@expo/env": "~2.4.3", - "@expo/json-file": "~11.0.1", - "@expo/metro": "~56.0.2", - "@expo/require-utils": "^57.0.5", - "@expo/spawn-async": "^1.8.0", - "@jridgewell/gen-mapping": "^0.3.13", - "@jridgewell/remapping": "^2.3.5", - "@jridgewell/sourcemap-codec": "^1.5.5", - "browserslist": "^4.25.0", - "chalk": "^4.1.0", - "debug": "^4.3.2", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "hermes-parser": "^0.36.0", - "jsc-safe-url": "^0.2.4", - "lightningcss": "^1.30.1", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "expo": "*" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/@expo/ws-tunnel": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-2.0.0.tgz", - "integrity": "sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==", - "license": "MIT", - "peerDependencies": { - "ws": "^8.0.0" - } - }, - "node_modules/expo/node_modules/@react-native/debugger-frontend": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.3.tgz", - "integrity": "sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==", - "license": "BSD-3-Clause", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/expo/node_modules/@react-native/debugger-shell": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.3.tgz", - "integrity": "sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/expo/node_modules/@react-native/dev-middleware": { - "version": "0.86.3", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.3.tgz", - "integrity": "sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.86.3", - "@react-native/debugger-shell": "0.86.3", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.3.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/expo/node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/expo/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/expo/node_modules/expo-asset": { - "version": "57.0.16", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.16.tgz", - "integrity": "sha512-IBRfQdW3iFT+GOBERMZLZM1MUNyrjMgMskuD0elVZ1ae44858UFlTJkHO3f8vi+u5zuv7O7KofsiN8NMG/uWzw==", - "license": "MIT", - "dependencies": { - "@expo/image-utils": "^0.11.5", - "expo-constants": "~57.0.17" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo/node_modules/expo-keep-awake": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-57.0.1.tgz", - "integrity": "sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*" - } - }, - "node_modules/expo/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/expo/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/expo/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/expo/node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/expo/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expo/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/expo/node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fb-dotslash": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", - "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "dotslash": "bin/dotslash" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fetch-nodeshim": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", - "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT" - }, - "node_modules/fontfaceobserver": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", - "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", - "license": "BSD-2-Clause" - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hermes-compiler": { - "version": "250829098.0.14", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", - "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", - "license": "MIT" - }, - "node_modules/hermes-estree": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", - "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", - "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.36.0" - } - }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/i18n-js": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.5.3.tgz", - "integrity": "sha512-5/tT6R9t9qlYqGhxGq9I9Ap3WKUaAMq5aRuO1gqAcUqm6xGbL0jwTAjSFjgbx935BAV8QbEzvQOzE796dUlEfA==", - "license": "MIT", - "dependencies": { - "bignumber.js": "*", - "lodash": "*", - "make-plural": "7.5.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jimp-compact": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz", - "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "license": "0BSD" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lan-network": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", - "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", - "license": "MIT", - "bin": { - "lan-network": "dist/lan-network-cli.js" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "license": "MIT", - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-plural": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.5.0.tgz", - "integrity": "sha512-0booA+aVYyVFoR67JBHdfVk0U08HmrBH2FrtmBqBa+NldlqXv/G2Z9VQuQq6Wgp2jDWdybEWGfBkk1cq5264WA==", - "license": "Unicode-DFS-2016" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/marky": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", - "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0" - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/metro": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.5.tgz", - "integrity": "sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "accepts": "^2.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "error-stack-parser": "^2.0.6", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.35.0", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.84.5", - "metro-cache": "0.84.5", - "metro-cache-key": "0.84.5", - "metro-config": "0.84.5", - "metro-core": "0.84.5", - "metro-file-map": "0.84.5", - "metro-resolver": "0.84.5", - "metro-runtime": "0.84.5", - "metro-source-map": "0.84.5", - "metro-symbolicate": "0.84.5", - "metro-transform-plugins": "0.84.5", - "metro-transform-worker": "0.84.5", - "mime-types": "^3.0.1", - "nullthrows": "^1.1.1", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "throat": "^5.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "metro": "src/cli.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-babel-transformer": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.5.tgz", - "integrity": "sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.35.0", - "metro-cache-key": "0.84.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT" - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.35.0" - } - }, - "node_modules/metro-cache": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.5.tgz", - "integrity": "sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==", - "license": "MIT", - "dependencies": { - "exponential-backoff": "^3.1.1", - "flow-enums-runtime": "^0.0.6", - "https-proxy-agent": "^7.0.5", - "metro-core": "0.84.5" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-cache-key": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.5.tgz", - "integrity": "sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-config": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.5.tgz", - "integrity": "sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==", - "license": "MIT", - "dependencies": { - "connect": "^3.6.5", - "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.7.0", - "metro": "0.84.5", - "metro-cache": "0.84.5", - "metro-core": "0.84.5", - "metro-runtime": "0.84.5", - "yaml": "^2.6.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-core": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.5.tgz", - "integrity": "sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.84.5" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-file-map": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.5.tgz", - "integrity": "sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "fb-watchman": "^2.0.0", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-minify-terser": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.5.tgz", - "integrity": "sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "terser": "^5.15.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-resolver": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.5.tgz", - "integrity": "sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-runtime": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.5.tgz", - "integrity": "sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.0", - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-source-map": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.5.tgz", - "integrity": "sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-symbolicate": "0.84.5", - "nullthrows": "^1.1.1", - "ob1": "0.84.5", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-symbolicate": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.5.tgz", - "integrity": "sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-source-map": "0.84.5", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-transform-plugins": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.5.tgz", - "integrity": "sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro-transform-worker": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.5.tgz", - "integrity": "sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "metro": "0.84.5", - "metro-babel-transformer": "0.84.5", - "metro-cache": "0.84.5", - "metro-cache-key": "0.84.5", - "metro-minify-terser": "0.84.5", - "metro-source-map": "0.84.5", - "metro-transform-plugins": "0.84.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT" - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.35.0" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/multitars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/multitars/-/multitars-1.0.2.tgz", - "integrity": "sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/node-addon-api": { - "version": "8.9.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", - "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", - "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", - "license": "ISC", - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "license": "MIT" - }, - "node_modules/ob1": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.5.tgz", - "integrity": "sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==", - "license": "MIT", - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "license": "MIT", - "dependencies": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/ora/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ora/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-png": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", - "integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==", - "license": "MIT", - "dependencies": { - "pngjs": "^3.3.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/plist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", - "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.9.10", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/plist/node_modules/@xmldom/xmldom": { - "version": "0.9.12", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", - "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", - "license": "MIT", - "engines": { - "node": ">=14.6" - } - }, - "node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.18", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "license": "MIT", - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", - "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", - "license": "MIT", - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.3" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, - "node_modules/react-freeze": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", - "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=17.0.0" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/react-native": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.0.tgz", - "integrity": "sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==", - "license": "MIT", - "dependencies": { - "@react-native/assets-registry": "0.86.0", - "@react-native/codegen": "0.86.0", - "@react-native/community-cli-plugin": "0.86.0", - "@react-native/gradle-plugin": "0.86.0", - "@react-native/js-polyfills": "0.86.0", - "@react-native/normalize-colors": "0.86.0", - "@react-native/virtualized-lists": "0.86.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "ansi-regex": "^5.0.0", - "babel-plugin-syntax-hermes-parser": "0.36.0", - "base64-js": "^1.5.1", - "commander": "^12.0.0", - "flow-enums-runtime": "^0.0.6", - "hermes-compiler": "250829098.0.14", - "invariant": "^2.2.4", - "memoize-one": "^5.0.0", - "metro-runtime": "^0.84.3", - "metro-source-map": "^0.84.3", - "nullthrows": "^1.1.1", - "pretty-format": "^29.7.0", - "promise": "^8.3.0", - "react-devtools-core": "^6.1.5", - "react-refresh": "^0.14.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "0.27.0", - "semver": "^7.1.3", - "stacktrace-parser": "^0.1.10", - "tinyglobby": "^0.2.15", - "whatwg-fetch": "^3.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "react-native": "cli.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@react-native/jest-preset": "0.86.0", - "@types/react": "^19.1.1", - "react": "^19.2.3" - }, - "peerDependenciesMeta": { - "@react-native/jest-preset": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-native-drawer-layout": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.10.tgz", - "integrity": "sha512-O6TQdZ5LSm3dqnuR4rX9KPtE+9dVg7jsezEYz1l01rkQTe4fQvYZIoPu2sZFl5X2N70uhRdjnPULySVR3sBUwA==", - "license": "MIT", - "dependencies": { - "color": "^4.2.3", - "use-latest-callback": "^0.2.4" - }, - "peerDependencies": { - "react": ">= 18.2.0", - "react-native": "*", - "react-native-gesture-handler": ">= 2.0.0", - "react-native-reanimated": ">= 2.0.0" - } - }, - "node_modules/react-native-gesture-handler": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-3.2.1.tgz", - "integrity": "sha512-VEWscxUN29aeccbD2McF0LSXrwOhSdisaSRFiEWg0O/3WrVghNJp/tOtLrCgzb9gfcZ3xdA+K2Sp8F5G3iHx/Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/react-test-renderer": "^19.1.0", - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-is-edge-to-edge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", - "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-reanimated": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.6.0.tgz", - "integrity": "sha512-9vbQmok5BPX2J3RLPFt2UnwmK2QQoMDPDi0VB4a9JWIiEc55pdzvwEsdH91V/JXKNErqM6a9fElTBMc/1cbtBA==", - "license": "MIT", - "peer": true, - "dependencies": { - "react-native-is-edge-to-edge": "^1.3.1", - "semver": "^7.7.3" - }, - "peerDependencies": { - "react": "*", - "react-native": "0.83 - 0.87", - "react-native-worklets": "0.12.x" - } - }, - "node_modules/react-native-reanimated/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-native-safe-area-context": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz", - "integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-screens": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.26.2.tgz", - "integrity": "sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==", - "license": "MIT", - "dependencies": { - "react-freeze": "^1.0.0", - "warn-once": "^0.1.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/react-native-worklets": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.12.2.tgz", - "integrity": "sha512-TB6Aze0WJg/U0XOu4Zfx/yV8i5i7C/lbMymUXPbq1izDy2nuzZ9ujQAMwAHM2Ygyhzi8sdRk/7FukuTTfuil9A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/generator": "^7.27.1", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/preset-typescript": "^7.28.5", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", - "convert-source-map": "^2.0.0", - "semver": "^7.7.4" - }, - "peerDependencies": { - "@babel/core": "*", - "@react-native/metro-config": "*", - "react": "*", - "react-native": "0.83 - 0.87" - } - }, - "node_modules/react-native-worklets/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-native/node_modules/@react-native/codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", - "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", - "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", - "license": "MIT", - "dependencies": { - "hermes-parser": "0.36.0" - } - }, - "node_modules/react-native/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/react-native/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-native/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT" - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", - "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-workspace-root": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz", - "integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==", - "license": "MIT" - }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "license": "MIT", - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/rtl-detect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==", - "license": "BSD-3-Clause" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/sandbox-cli-detector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sandbox-cli-detector/-/sandbox-cli-detector-0.2.0.tgz", - "integrity": "sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==", - "license": "MIT", - "bin": { - "sandbox-cli-detector": "dist/cli.js" - }, - "engines": { - "node": ">=18.18" - } - }, - "node_modules/sax": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/server-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", - "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sf-symbols-typescript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", - "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", - "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/simple-plist": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", - "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", - "license": "MIT", - "dependencies": { - "bplist-creator": "0.1.0", - "bplist-parser": "0.3.1", - "plist": "^3.0.5" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/slugify": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", - "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" - }, - "node_modules/stacktrace-parser": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", - "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/standard-navigation": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.5.tgz", - "integrity": "sha512-YAmzwAiiQVocZxO/VGPFiQHcu5pKiz09QIGC0MK6aRMoa3E0QkoTQgcqJr7ZZ3OMiNhu4DkaGElFI5htjOIDbw==", - "license": "MIT" - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-buffers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", - "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", - "license": "Unlicense", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/structured-headers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", - "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.51.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", - "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toqr": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz", - "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.23.13", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", - "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-latest-callback": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", - "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vaul": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", - "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "license": "MIT" - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/warn-once": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", - "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", - "license": "MIT" - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT" - }, - "node_modules/whatwg-url-minimum": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.2.tgz", - "integrity": "sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==", - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xcode": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", - "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", - "license": "Apache-2.0", - "dependencies": { - "simple-plist": "^1.1.0", - "uuid": "^7.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/xml2js": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", - "integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/mobile/package.json b/mobile/package.json deleted file mode 100644 index 0bb3f8e..0000000 --- a/mobile/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "webui", - "version": "2.0.0", - "main": "expo-router/entry", - "dependencies": { - "@expo/vector-icons": "^15.1.1", - "@tanstack/react-query": "^5.102.8", - "expo": "~57.0.8", - "expo-constants": "~57.0.17", - "expo-file-system": "~57.0.6", - "expo-linking": "~57.0.9", - "expo-localization": "~57.0.1", - "expo-router": "~57.0.20", - "expo-secure-store": "~57.0.3", - "expo-sqlite": "~57.0.2", - "expo-status-bar": "~57.0.1", - "i18n-js": "^4.5.3", - "react": "19.2.3", - "react-dom": "19.2.3", - "react-native": "0.86.0", - "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "~4.26.0" - }, - "devDependencies": { - "@types/better-sqlite3": "^9.6.0", - "@types/react": "~19.2.2", - "babel-preset-expo": "^57.0.4", - "better-sqlite3": "^13.0.3", - "tsx": "^4.23.13", - "typescript": "~6.0.3" - }, - "scripts": { - "start": "expo start", - "android": "expo run:android", - "ios": "expo run:ios", - "web": "expo start --web", - "test:db": "tsx --test tests/migrations.test.ts tests/repositories.test.ts tests/dbClient.test.ts", - "test:migrations": "tsx --test tests/migrations.test.ts", - "test:api": "tsx --test tests/apiClient.test.ts", - "test:sync": "tsx --test tests/syncOutbox.test.ts", - "test:saf": "tsx --test tests/safWalk.test.ts", - "test:syncDevice": "tsx --test tests/syncDevice.test.ts", - "test:e2e": "tsx --test tests/e2e.live.test.ts", - "test": "npm run test:db && npm run test:api && npm run test:sync && npm run test:saf && npm run test:syncDevice" - }, - "private": true -} diff --git a/mobile/services/db/client.ts b/mobile/services/db/client.ts deleted file mode 100644 index fd5ad23..0000000 --- a/mobile/services/db/client.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { SQLiteDatabase } from 'expo-sqlite'; -import { migrateDatabase } from './migrations'; -import { DATABASE_NAME } from './schema'; - -let database: SQLiteDatabase | null = null; -let opening: Promise | null = null; - -async function loadSqlite(): Promise { - return import('expo-sqlite'); -} - -/** - * expo-sqlite (Android, encore non corrigé en 57.x, cf. expo/expo#48999) peut - * « empoisonner » une connexion : après un teardown de runtime (reload dev, - * reconstitution d'Activity) de vieux handles natifs sont double-fermés et - * chaque requête suivante rejette un NullPointerException nu. Un simple reopen - * renvoie l'objet empoisonné en cache ; seul un reconnect (useNewConnection) - * redonne une connexion vive. - */ -export function isBrokenConnectionError(error: unknown): boolean { - if (!(error instanceof Error)) return false; - return ( - error.message.includes("NativeDatabase.prepareAsync") || - error.message.includes("NativeDatabase.execAsync") || - error.message.includes("NullPointerException") - ); -} - -async function openDatabase(useNewConnection: boolean): Promise { - const SQLite = await loadSqlite(); - const db = await SQLite.openDatabaseAsync( - DATABASE_NAME, - useNewConnection ? { useNewConnection: true } : {}, - ); - await migrateDatabase(db); - return db; -} - -export function getDatabase(): Promise { - if (database) return Promise.resolve(database); - if (!opening) { - opening = openDatabase(false) - .then((db) => { - database = db; - return db; - }) - .finally(() => { - opening = null; - }); - } - return opening; -} - -export async function recoverDatabase(): Promise { - const poisoned = database; - database = null; - opening = null; - if (poisoned) { - try { - await poisoned.closeAsync(); - } catch { - // Handle déjà corrompu : se contenter de le lâcher, le reconnect suffit. - } - } - const fresh = await openDatabase(true); - database = fresh; - return fresh; -} - -export type DatabaseRetryDeps = { - get(): Promise; - recover(): Promise; -}; - -const defaultRetryDeps: DatabaseRetryDeps = { - get: getDatabase, - recover: recoverDatabase, -}; - -export async function withDatabaseRetry( - run: (db: SQLiteDatabase) => Promise, - deps: DatabaseRetryDeps = defaultRetryDeps, -): Promise { - try { - const db = await deps.get(); - return await run(db); - } catch (error) { - if (!isBrokenConnectionError(error)) throw error; - const fresh = await deps.recover(); - return await run(fresh); - } -} - -export async function closeDatabase(): Promise { - if (!database) return; - await database.closeAsync(); - database = null; - opening = null; -} - -export async function checkpointDatabase(): Promise { - try { - const db = await getDatabase(); - await db.execAsync('PRAGMA wal_checkpoint(TRUNCATE)'); - } catch { - // Best-effort : ne jamais faire échouer la sync sur un checkpoint. - } -} - -type WithTransactionImpl = (work: (db: SQLiteDatabase) => Promise) => Promise; - -let withTransactionOverride: WithTransactionImpl | null = null; - -/** Test-only seam : les tests Node n'ont pas expo-sqlite, `syncRoot`/`syncDevice` - * substituent leur transaction par un simple `work()`. `null` en production. */ -export function __setWithTransactionForTests(impl: WithTransactionImpl | null): void { - withTransactionOverride = impl; -} - -export async function withTransaction( - work: (db: SQLiteDatabase) => Promise, -): Promise { - if (withTransactionOverride) return withTransactionOverride(work); - return withDatabaseRetry(async (db) => { - let result!: T; - await db.withTransactionAsync(async () => { - result = await work(db); - }); - return result; - }); -} \ No newline at end of file diff --git a/mobile/services/db/id.ts b/mobile/services/db/id.ts deleted file mode 100644 index 18d1dcf..0000000 --- a/mobile/services/db/id.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getSession } from './session'; - -export async function newResourceId(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync<{ id: string }>( - 'SELECT lower(hex(randomblob(16))) AS id', - ); - return row!.id; -} \ No newline at end of file diff --git a/mobile/services/db/index.ts b/mobile/services/db/index.ts deleted file mode 100644 index f6ba47c..0000000 --- a/mobile/services/db/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -export { - getDatabase, - closeDatabase, - withTransaction, - checkpointDatabase, -} from './client'; -export { migrateDatabase, type Migration, type MigrationDb } from './migrations'; -export * from './repositories'; -export type { AccessCheck, AccessSource } from './repositories/permissions'; -export { - transitionSyncStatus, - type SyncEvent, - type SyncTransitionResult, -} from './transitions'; -export type { - AccessLevel, - NewPendingOperation, - NewResourcePermission, - NewShare, - NewShareLink, - PendingOperation, - PendingOperationRefType, - PendingOperationRow, - PendingOperationStatus, - PendingOperationType, - PushStatus, - Recipient, - RecipientRow, - RecipientType, - ResourcePermission, - ResourceType, - Share, - ShareLink, - ShareLinkRow, - ShareRow, - StoredFile, - StoredFolder, - SyncStatus, - UserPreferences, - FolderRow, - FileRow, -} from './types'; -export { - DATABASE_NAME, - DATABASE_VERSION, - DEVICE_USER_ID_KEY, - AUTH_TOKEN_KEY, - ACTIVE_USER_ID_KEY, - PREFERENCES_KEY, - PERMISSION_TTL_MS, -} from './schema'; \ No newline at end of file diff --git a/mobile/services/db/migrations.ts b/mobile/services/db/migrations.ts deleted file mode 100644 index 151f277..0000000 --- a/mobile/services/db/migrations.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { DATABASE_VERSION, DEVICE_USER_ID_KEY } from './schema'; - -export type MigrationDb = { - execAsync(source: string): Promise; - getFirstAsync(source: string, ...params: unknown[]): Promise; - withExclusiveTransactionAsync(task: (txn: MigrationDb) => Promise): Promise; -}; - -export type Migration = { - version: number; - up: (db: MigrationDb) => Promise; -}; - -export const MIGRATIONS: Migration[] = [ - { - version: 1, - up: async (db) => { - await db.execAsync(` -PRAGMA journal_mode = 'wal'; -PRAGMA foreign_keys = ON; -CREATE TABLE IF NOT EXISTS folders ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - uri TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - "exists" INTEGER, - sync_status TEXT NOT NULL DEFAULT 'local', - added_at INTEGER NOT NULL, - parent_uri TEXT -); -CREATE TABLE IF NOT EXISTS files ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - uri TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - folder_uri TEXT NOT NULL, - extension TEXT, - size INTEGER, - "type" TEXT, - "exists" INTEGER, - last_modified INTEGER, - sync_status TEXT NOT NULL DEFAULT 'local', - added_at INTEGER NOT NULL, - FOREIGN KEY (folder_uri) REFERENCES folders(uri) ON DELETE CASCADE -); -CREATE TABLE IF NOT EXISTS user_preferences ( - "key" TEXT PRIMARY KEY NOT NULL, - "value" TEXT NOT NULL, - updated_at INTEGER NOT NULL -); -`); - }, - }, - { - version: 2, - up: async (db) => { - await db.execAsync(` -CREATE INDEX IF NOT EXISTS idx_files_folder_uri ON files(folder_uri); -`); - }, - }, - { - version: 3, - up: async (db) => { - await db.execAsync(` -CREATE TABLE IF NOT EXISTS resource_permissions ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - resource_id TEXT NOT NULL, - resource_type TEXT NOT NULL CHECK (resource_type IN ('folder', 'file')), - effective_access TEXT NOT NULL CHECK (effective_access IN ('owner', 'editor', 'commenter', 'viewer')), - inherit INTEGER NOT NULL DEFAULT 1, - owner_id TEXT, - shared_by_id TEXT, - expires_at INTEGER, - cached_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE (resource_id, resource_type) -); -CREATE TABLE IF NOT EXISTS shares ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - resource_id TEXT NOT NULL, - resource_type TEXT NOT NULL CHECK (resource_type IN ('folder', 'file')), - recipient_type TEXT NOT NULL CHECK (recipient_type IN ('user', 'group')), - recipient_id TEXT NOT NULL, - relation TEXT NOT NULL CHECK (relation IN ('owner', 'editor', 'commenter', 'viewer')), - inherit INTEGER NOT NULL DEFAULT 1, - expires_at INTEGER, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE (resource_id, resource_type, recipient_type, recipient_id) -); -CREATE TABLE IF NOT EXISTS recipients ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - recipient_type TEXT NOT NULL CHECK (recipient_type IN ('user', 'group')), - recipient_id TEXT NOT NULL UNIQUE, - display_name TEXT NOT NULL, - is_active INTEGER NOT NULL DEFAULT 1, - updated_at INTEGER NOT NULL -); -CREATE TABLE IF NOT EXISTS share_links ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - token TEXT NOT NULL UNIQUE, - resource_id TEXT NOT NULL, - resource_type TEXT NOT NULL CHECK (resource_type IN ('folder', 'file')), - has_password INTEGER NOT NULL DEFAULT 0, - allow_download INTEGER NOT NULL DEFAULT 1, - expires_at INTEGER, - max_downloads INTEGER, - downloads_count INTEGER NOT NULL DEFAULT 0, - is_revoked INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - CHECK (max_downloads IS NULL OR max_downloads >= 0), - CHECK (downloads_count >= 0) -); -CREATE TABLE IF NOT EXISTS pending_operations ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - resource_id TEXT, - resource_type TEXT CHECK (resource_type IN ('folder', 'file')), - ref_type TEXT CHECK (ref_type IN ('resource', 'share', 'share_link')), - ref_id INTEGER, - operation TEXT NOT NULL CHECK (operation IN ( - 'create_resource', 'update_metadata', 'delete_resource', 'move_resource', - 'share', 'revoke_share', 'update_share', - 'create_link', 'revoke_link' - )), - payload TEXT NOT NULL DEFAULT '{}', - status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed', 'failed', 'cancelled')), - attempts INTEGER NOT NULL DEFAULT 0, - error TEXT, - created_at INTEGER NOT NULL, - next_retry_at INTEGER, - last_error_at INTEGER -); -CREATE INDEX IF NOT EXISTS idx_pending_queue ON pending_operations(created_at) WHERE status = 'pending'; -CREATE INDEX IF NOT EXISTS idx_pending_resource ON pending_operations(resource_id); -`); - await db.execAsync( - `INSERT OR IGNORE INTO user_preferences ("key", "value", updated_at) - VALUES ('${DEVICE_USER_ID_KEY}', lower(hex(randomblob(16))), ${Date.now()});`, - ); - }, - }, - { - version: 4, - up: async (db) => { - await db.execAsync('PRAGMA foreign_keys = OFF;'); - await db.withExclusiveTransactionAsync(async (txn) => { - await txn.execAsync(` -CREATE TABLE folders_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - resource_id TEXT NOT NULL UNIQUE, - uri TEXT, - name TEXT NOT NULL, - "exists" INTEGER, - parent_resource_id TEXT REFERENCES folders_new(resource_id) ON DELETE CASCADE, - owner_id TEXT NOT NULL, - sync_status TEXT NOT NULL DEFAULT 'local', - added_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL -); -`); - await txn.execAsync(` -INSERT INTO folders_new (id, resource_id, uri, name, "exists", parent_resource_id, owner_id, sync_status, added_at, updated_at) -SELECT id, lower(hex(randomblob(16))), uri, name, "exists", NULL, - (SELECT "value" FROM user_preferences WHERE "key" = '${DEVICE_USER_ID_KEY}'), - sync_status, added_at, added_at FROM folders; -`); - await txn.execAsync(` -UPDATE folders_new SET parent_resource_id = ( - SELECT parent_new.resource_id - FROM folders old_child - JOIN folders old_parent ON old_parent.uri = old_child.parent_uri - JOIN folders_new parent_new ON parent_new.uri = old_parent.uri - WHERE old_child.uri = folders_new.uri -); -`); - await txn.execAsync('DROP TABLE folders;'); - await txn.execAsync('ALTER TABLE folders_new RENAME TO folders;'); - await txn.execAsync(` -CREATE TABLE files_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - resource_id TEXT NOT NULL UNIQUE, - uri TEXT, - name TEXT NOT NULL, - folder_resource_id TEXT NOT NULL REFERENCES folders(resource_id) ON DELETE CASCADE, - extension TEXT, - size INTEGER, - "type" TEXT, - "exists" INTEGER, - last_modified INTEGER, - owner_id TEXT NOT NULL, - sync_status TEXT NOT NULL DEFAULT 'local', - added_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL -); -`); - await txn.execAsync(` -INSERT INTO files_new (id, resource_id, uri, name, folder_resource_id, extension, size, "type", "exists", last_modified, owner_id, sync_status, added_at, updated_at) -SELECT f.id, lower(hex(randomblob(16))), f.uri, f.name, - fn.resource_id, - f.extension, f.size, f."type", f."exists", f.last_modified, - (SELECT "value" FROM user_preferences WHERE "key" = '${DEVICE_USER_ID_KEY}'), - f.sync_status, f.added_at, f.added_at -FROM files f -JOIN folders fn ON fn.uri = f.folder_uri; -`); - await txn.execAsync('DROP TABLE files;'); - await txn.execAsync('ALTER TABLE files_new RENAME TO files;'); - await txn.execAsync(` -CREATE INDEX IF NOT EXISTS idx_folders_resource_id ON folders(resource_id); -CREATE INDEX IF NOT EXISTS idx_files_resource_id ON files(resource_id); -CREATE INDEX IF NOT EXISTS idx_files_folder_resource ON files(folder_resource_id); -CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_resource_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_folders_uri ON folders(uri) WHERE uri IS NOT NULL; -CREATE UNIQUE INDEX IF NOT EXISTS idx_files_uri ON files(uri) WHERE uri IS NOT NULL; -`); - await txn.execAsync(`PRAGMA user_version = 4;`); - }); - await db.execAsync('PRAGMA foreign_keys = ON;'); - }, - }, - { - // User-first : pending_operations et resource_permissions deviennent - // scopés par compte (`user_id`), NULL = entrées legacy/device-local. Le - // UNIQUE de resource_permissions passe à (user_id, resource_id, - // resource_type) pour qu'un même fichier partagé à deux comptes ne - // collisionne pas côté cache. SQLite ne pouvant pas altérer un UNIQUE, la - // table est reconstruite (le schéma v5 se base sur v3/v4 — l'indice - // UNIQUE (resource_id, resource_type) a été posé en v3). - version: 5, - up: async (db) => { - await db.execAsync('PRAGMA foreign_keys = OFF;'); - await db.withExclusiveTransactionAsync(async (txn) => { - await txn.execAsync(` -CREATE TABLE resource_permissions_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - user_id TEXT, - resource_id TEXT NOT NULL, - resource_type TEXT NOT NULL CHECK (resource_type IN ('folder', 'file')), - effective_access TEXT NOT NULL CHECK (effective_access IN ('owner', 'editor', 'commenter', 'viewer')), - inherit INTEGER NOT NULL DEFAULT 1, - owner_id TEXT, - shared_by_id TEXT, - expires_at INTEGER, - cached_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE (user_id, resource_id, resource_type) -); -INSERT INTO resource_permissions_new (id, user_id, resource_id, resource_type, effective_access, inherit, owner_id, shared_by_id, expires_at, cached_at, updated_at) -SELECT id, NULL, resource_id, resource_type, effective_access, inherit, owner_id, shared_by_id, expires_at, cached_at, updated_at FROM resource_permissions; -DROP TABLE resource_permissions; -ALTER TABLE resource_permissions_new RENAME TO resource_permissions; -CREATE INDEX IF NOT EXISTS idx_permissions_resource ON resource_permissions(resource_id, resource_type); -`); - await txn.execAsync(` -ALTER TABLE pending_operations ADD COLUMN user_id TEXT; -`); - await txn.execAsync(`PRAGMA user_version = 5;`); - }); - await db.execAsync('PRAGMA foreign_keys = ON;'); - }, - }, -]; - -async function readUserVersion(db: MigrationDb): Promise { - const row = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version'); - return row?.user_version ?? 0; -} - -async function writeUserVersion(db: MigrationDb, version: number): Promise { - await db.execAsync(`PRAGMA user_version = ${version}`); -} - -const migrationPromises = new WeakMap>(); - -export async function migrateDatabase( - db: MigrationDb, - targetVersion: number = DATABASE_VERSION, -): Promise { - const existing = migrationPromises.get(db); - if (existing) return existing; - - const promise = runMigrations(db, targetVersion).finally(() => { - migrationPromises.delete(db); - }); - migrationPromises.set(db, promise); - return promise; -} - -async function runMigrations(db: MigrationDb, targetVersion: number): Promise { - let currentVersion = await readUserVersion(db); - - for (const migration of MIGRATIONS) { - if (migration.version > currentVersion && migration.version <= targetVersion) { - await migration.up(db); - await writeUserVersion(db, migration.version); - currentVersion = migration.version; - } - } -} \ No newline at end of file diff --git a/mobile/services/db/repositories/files.ts b/mobile/services/db/repositories/files.ts deleted file mode 100644 index 786dd3c..0000000 --- a/mobile/services/db/repositories/files.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { getSession } from '../session'; -import { newResourceId } from '../id'; -import { FILE_COLUMNS_SQL } from '../schema'; -import { getDeviceUserId } from './preferences'; -import type { FileEntry, FileRow, StoredFile, SyncStatus } from '../types'; - -export type SaveFileOptions = { - syncStatus?: SyncStatus; - resource_id?: string; -}; - -export async function saveFile( - file: FileEntry, - folderResourceId: string, - options: SaveFileOptions = {}, -): Promise { - const db = await getSession(); - const now = Date.now(); - const exists = file.exists ? 1 : 0; - const ownerId = await getDeviceUserId(); - - const existing = options.resource_id - ? await db.getFirstAsync( - `SELECT ${FILE_COLUMNS_SQL} FROM files WHERE resource_id = ?`, - options.resource_id, - ) - : await db.getFirstAsync( - `SELECT ${FILE_COLUMNS_SQL} FROM files WHERE uri = ?`, - file.uri, - ); - - const resourceId = existing?.resource_id ?? (await newResourceId()); - const baseSync = existing?.sync_status ?? options.syncStatus ?? 'local'; - - await db.runAsync( - `INSERT INTO files - (resource_id, uri, name, folder_resource_id, extension, size, "type", "exists", last_modified, owner_id, sync_status, added_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(resource_id) DO UPDATE SET - uri = excluded.uri, - name = excluded.name, - folder_resource_id = excluded.folder_resource_id, - extension = excluded.extension, - size = excluded.size, - "type" = excluded."type", - "exists" = excluded."exists", - last_modified = excluded.last_modified, - sync_status = excluded.sync_status, - updated_at = excluded.updated_at`, - resourceId, - file.uri, - file.name, - folderResourceId, - file.extension ?? null, - file.size, - file.type ?? null, - exists, - file.lastModified ?? null, - ownerId, - baseSync, - existing?.added_at ?? now, - now, - ); - - const row = await db.getFirstAsync( - `SELECT ${FILE_COLUMNS_SQL} FROM files WHERE resource_id = ?`, - resourceId, - ); - return toStoredFile(row!); -} - -export async function getFiles(folderResourceId?: string): Promise { - const db = await getSession(); - const rows = - folderResourceId === undefined - ? await db.getAllAsync(`SELECT ${FILE_COLUMNS_SQL} FROM files ORDER BY name ASC`) - : await db.getAllAsync( - `SELECT ${FILE_COLUMNS_SQL} FROM files WHERE folder_resource_id = ? ORDER BY name ASC`, - folderResourceId, - ); - return rows.map(toStoredFile); -} - -export async function getFile(resourceId: string): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT ${FILE_COLUMNS_SQL} FROM files WHERE resource_id = ?`, - resourceId, - ); - return row ? toStoredFile(row) : null; -} - -export async function removeFile(resourceId: string): Promise { - const db = await getSession(); - await db.runAsync('DELETE FROM files WHERE resource_id = ?', resourceId); -} - -function toStoredFile(row: FileRow): StoredFile { - return { - resource_id: row.resource_id, - uri: row.uri, - name: row.name, - folder_resource_id: row.folder_resource_id, - extension: row.extension ?? '', - exists: row.exists === 1, - size: row.size, - type: row.type ?? '', - lastModified: row.last_modified, - owner_id: row.owner_id, - syncStatus: row.sync_status, - addedAt: row.added_at, - updatedAt: row.updated_at, - }; -} \ No newline at end of file diff --git a/mobile/services/db/repositories/folders.ts b/mobile/services/db/repositories/folders.ts deleted file mode 100644 index a1bb73a..0000000 --- a/mobile/services/db/repositories/folders.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { getSession } from '../session'; -import { newResourceId } from '../id'; -import { FOLDER_COLUMNS_SQL } from '../schema'; -import { getDeviceUserId } from './preferences'; -import type { FolderRow, StoredFolder, SyncStatus } from '../types'; - -export type SaveFolderInput = { - uri: string | null; - name: string; - exists?: boolean; - resource_id?: string; -}; - -export type SaveFolderOptions = { - parentResourceId?: string | null; - syncStatus?: SyncStatus; -}; - -export async function saveFolder( - input: SaveFolderInput, - options: SaveFolderOptions = {}, -): Promise { - const db = await getSession(); - const now = Date.now(); - const exists = - input.exists === undefined || input.exists === null ? null : input.exists ? 1 : 0; - const ownerId = await getDeviceUserId(); - - const existing = input.resource_id - ? await db.getFirstAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE resource_id = ?`, - input.resource_id, - ) - : input.uri - ? await db.getFirstAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE uri = ?`, - input.uri, - ) - : null; - - const resourceId = existing?.resource_id ?? input.resource_id ?? (await newResourceId()); - const baseSync = existing?.sync_status ?? options.syncStatus ?? 'local'; - - await db.runAsync( - `INSERT INTO folders - (resource_id, uri, name, "exists", parent_resource_id, owner_id, sync_status, added_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(resource_id) DO UPDATE SET - uri = excluded.uri, - name = excluded.name, - "exists" = excluded."exists", - parent_resource_id = CASE - WHEN excluded.parent_resource_id IS NULL THEN folders.parent_resource_id - ELSE excluded.parent_resource_id - END, - sync_status = excluded.sync_status, - updated_at = excluded.updated_at`, - resourceId, - input.uri, - input.name, - exists, - options.parentResourceId ?? null, - ownerId, - baseSync, - existing?.added_at ?? now, - now, - ); - - const row = await db.getFirstAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE resource_id = ?`, - resourceId, - ); - return toStoredFolder(row!); -} - -export async function saveDirectory(folder: { - uri: string; - name: string; - exists?: boolean; -}): Promise { - return saveFolder({ uri: folder.uri, name: folder.name, exists: folder.exists }); -} - -export async function getFolders(): Promise { - const db = await getSession(); - const rows = await db.getAllAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders ORDER BY name ASC`, - ); - return rows.map(toStoredFolder); -} - -export async function getFolderFolders( - parentResourceId: string | null, -): Promise { - const db = await getSession(); - const rows = await db.getAllAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE parent_resource_id IS ? ORDER BY name ASC`, - parentResourceId, - ); - return rows.map(toStoredFolder); -} - -export async function getFolder(resourceId: string): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT ${FOLDER_COLUMNS_SQL} FROM folders WHERE resource_id = ?`, - resourceId, - ); - return row ? toStoredFolder(row) : null; -} - -export async function removeFolder(resourceId: string): Promise { - const db = await getSession(); - await db.runAsync('DELETE FROM folders WHERE resource_id = ?', resourceId); -} - -function toStoredFolder(row: FolderRow): StoredFolder { - return { - resource_id: row.resource_id, - uri: row.uri, - name: row.name, - exists: row.exists === 1, - parent_resource_id: row.parent_resource_id, - owner_id: row.owner_id, - syncStatus: row.sync_status, - addedAt: row.added_at, - updatedAt: row.updated_at, - }; -} \ No newline at end of file diff --git a/mobile/services/db/repositories/index.ts b/mobile/services/db/repositories/index.ts deleted file mode 100644 index 64bb5e3..0000000 --- a/mobile/services/db/repositories/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -export { - saveFolder, - saveDirectory, - getFolders, - getFolderFolders, - getFolder, - removeFolder, -} from './folders'; -export { saveFile, getFiles, getFile, removeFile } from './files'; -export { - saveUserPreferences, - getUserPreferences, - getDeviceUserId, - getDeviceAuthToken, - saveDeviceAuthToken, - getActiveUserId, - setActiveUserId, - clearActiveUserId, -} from './preferences'; -export { - getResourcePermission, - saveResourcePermission, - canAccess, - canWrite, - isOwner, -} from './permissions'; -export { saveShare, getShare, getShares, removeShare, removeSharesForResource } from './shares'; -export { - createShareLink, - getShareLinkById, - getShareLinkByToken, - getShareLinks, - incrementLinkDownloads, - revokeShareLink, - removeShareLinksForResource, -} from './shareLinks'; -export { saveRecipient, getRecipients, setRecipientActive } from './recipients'; -export { - enqueuePendingOperation, - getPendingOperations, - getNextQueuedOperation, - listQueuedOperations, - scheduleRetries, - markPendingOperation, - MAX_PENDING_ATTEMPTS, -} from './pendingOps'; \ No newline at end of file diff --git a/mobile/services/db/repositories/pendingOps.ts b/mobile/services/db/repositories/pendingOps.ts deleted file mode 100644 index 2b05eae..0000000 --- a/mobile/services/db/repositories/pendingOps.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { getSession } from '../session'; -import { getActiveUserId } from './preferences'; -import type { - NewPendingOperation, - PendingOperation, - PendingOperationRow, - PendingOperationStatus, -} from '../types'; - -const PENDING_OPERATION_COLUMNS = ` - id, resource_id, resource_type, ref_type, ref_id, operation, payload, - status, attempts, error, created_at, next_retry_at, last_error_at`; - -const MAX_BACKOFF_MS = 24 * 60 * 60 * 1000; -const BASE_BACKOFF_MS = 30 * 1000; - -export const MAX_PENDING_ATTEMPTS = 5; - -export async function enqueuePendingOperation( - operation: NewPendingOperation, -): Promise { - const db = await getSession(); - const activeUserId = await getActiveUserId(); - const row = await db.getFirstAsync<{ id: number }>( - `INSERT INTO pending_operations - (resource_id, resource_type, ref_type, ref_id, operation, payload, status, attempts, created_at, user_id) - VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?) - RETURNING id`, - operation.resourceId ?? null, - operation.resourceType ?? null, - operation.refType ?? null, - operation.refId ?? null, - operation.operation, - JSON.stringify(operation.payload ?? {}), - Date.now(), - activeUserId, - ); - return row!.id; -} - -export async function getPendingOperations( - status?: PendingOperationStatus, -): Promise { - const db = await getSession(); - const rows = status - ? await db.getAllAsync( - `SELECT ${PENDING_OPERATION_COLUMNS} FROM pending_operations - WHERE status = ? AND (user_id = ? OR user_id IS NULL) - ORDER BY created_at ASC, id ASC`, - status, - await getActiveUserId(), - ) - : await db.getAllAsync( - `SELECT ${PENDING_OPERATION_COLUMNS} FROM pending_operations - WHERE user_id = ? OR user_id IS NULL - ORDER BY created_at ASC, id ASC`, - await getActiveUserId(), - ); - return rows.map(toPendingOperation); -} - -export async function getNextQueuedOperation(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT ${PENDING_OPERATION_COLUMNS} FROM pending_operations - WHERE status = 'pending' - AND (user_id = ? OR user_id IS NULL) - AND (next_retry_at IS NULL OR next_retry_at <= ?) - ORDER BY created_at ASC, id ASC - LIMIT 1`, - await getActiveUserId(), - Date.now(), - ); - return row ? toPendingOperation(row) : null; -} - -// Même chose mais en batch (tri FIFO identique) — utilisé par pushPendingOps. -export async function listQueuedOperations(limit: number): Promise { - if (limit <= 0) return []; - const db = await getSession(); - const rows = await db.getAllAsync( - `SELECT ${PENDING_OPERATION_COLUMNS} FROM pending_operations - WHERE status = 'pending' - AND (user_id = ? OR user_id IS NULL) - AND (next_retry_at IS NULL OR next_retry_at <= ?) - ORDER BY created_at ASC, id ASC - LIMIT ?`, - await getActiveUserId(), - Date.now(), - limit, - ); - return rows.map(toPendingOperation); -} - -// Reporter des opérations sans incrémenter attempts (utilisé en cas d'erreur -// transitoire réseau/serveur, pour éviter les dead-letters prématurées). -export async function scheduleRetries(ids: number[], retryAt: number): Promise { - const db = await getSession(); - const now = Date.now(); - for (const id of ids) { - await db.runAsync( - `UPDATE pending_operations SET next_retry_at = ?, last_error_at = ? WHERE id = ?`, - retryAt, - now, - id, - ); - } -} - -export async function markPendingOperation( - id: number, - status: PendingOperationStatus, - error?: string | null, -): Promise { - const db = await getSession(); - const now = Date.now(); - - if (status === 'failed') { - const current = await db.getFirstAsync<{ attempts: number }>( - 'SELECT attempts FROM pending_operations WHERE id = ?', - id, - ); - if (!current) return; - const attempts = current.attempts + 1; - if (attempts >= MAX_PENDING_ATTEMPTS) { - await db.runAsync( - `UPDATE pending_operations SET - status = 'failed', attempts = ?, error = ?, next_retry_at = NULL, last_error_at = ? - WHERE id = ?`, - attempts, - error ?? null, - now, - id, - ); - return; - } - const backoff = Math.min(BASE_BACKOFF_MS * 2 ** attempts, MAX_BACKOFF_MS); - await db.runAsync( - `UPDATE pending_operations SET - status = 'pending', attempts = ?, error = ?, next_retry_at = ?, last_error_at = ? - WHERE id = ?`, - attempts, - error ?? null, - now + backoff, - now, - id, - ); - return; - } - - await db.runAsync( - `UPDATE pending_operations SET status = ?, error = ?, next_retry_at = NULL, last_error_at = NULL WHERE id = ?`, - status, - status === 'in_progress' ? null : error ?? null, - id, - ); -} - -function toPendingOperation(row: PendingOperationRow): PendingOperation { - return { - id: row.id, - resourceId: row.resource_id, - resourceType: row.resource_type, - refType: row.ref_type, - refId: row.ref_id, - operation: row.operation, - payload: safeParse(row.payload), - status: row.status, - attempts: row.attempts, - error: row.error, - createdAt: row.created_at, - nextRetryAt: row.next_retry_at, - lastErrorAt: row.last_error_at, - }; -} - -function safeParse(json: string): unknown { - try { - return JSON.parse(json); - } catch { - return {}; - } -} \ No newline at end of file diff --git a/mobile/services/db/repositories/permissions.ts b/mobile/services/db/repositories/permissions.ts deleted file mode 100644 index 8f386d3..0000000 --- a/mobile/services/db/repositories/permissions.ts +++ /dev/null @@ -1,249 +0,0 @@ -import type { DbSession } from '../session'; -import { getSession } from '../session'; -import { PERMISSION_TTL_MS } from '../schema'; -import { getActiveUserId, getDeviceUserId } from './preferences'; -import type { - AccessLevel, - NewResourcePermission, - ResourcePermission, - ResourcePermissionRow, - ResourceType, -} from '../types'; - -const ACCESS_RANK: Record = { - viewer: 1, - commenter: 2, - editor: 3, - owner: 4, -}; - -export type AccessSource = 'none' | 'cache' | 'owner' | 'inherited'; - -export type AccessCheck = { - allowed: boolean; - access: AccessLevel | null; - source: AccessSource; - stale: boolean; - expiresAt: number | null; -}; - -type LineageNode = { - resource_id: string; - resource_type: ResourceType; - parent_resource_id: string | null; - owner_id: string; -}; - -function rank(level: AccessLevel): number { - return ACCESS_RANK[level]; -} - -export async function getResourcePermission( - resourceId: string, - resourceType: ResourceType, -): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT * FROM resource_permissions - WHERE resource_id = ? AND resource_type = ? AND (user_id = ? OR user_id IS NULL)`, - resourceId, - resourceType, - await getActiveUserId(), - ); - return row ? toResourcePermission(row) : null; -} - -export async function saveResourcePermission( - permission: NewResourcePermission, -): Promise { - const db = await getSession(); - const now = Date.now(); - await db.runAsync( - `INSERT INTO resource_permissions - (user_id, resource_id, resource_type, effective_access, inherit, owner_id, shared_by_id, expires_at, cached_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(user_id, resource_id, resource_type) DO UPDATE SET - effective_access = excluded.effective_access, - inherit = excluded.inherit, - owner_id = excluded.owner_id, - shared_by_id = excluded.shared_by_id, - expires_at = excluded.expires_at, - cached_at = excluded.cached_at, - updated_at = excluded.updated_at`, - await getActiveUserId(), - permission.resource_id, - permission.resourceType, - permission.effectiveAccess, - permission.inherit === false ? 0 : 1, - permission.ownerId ?? null, - permission.sharedById ?? null, - permission.expiresAt ?? null, - now, - now, - ); -} - -async function resourceLineage( - resourceId: string, - resourceType: ResourceType, -): Promise { - const db = await getSession(); - - if (resourceType === 'file') { - const file = await db.getFirstAsync<{ - folder_resource_id: string; - owner_id: string; - }>( - 'SELECT folder_resource_id, owner_id FROM files WHERE resource_id = ?', - resourceId, - ); - if (!file) return []; - return [ - { - resource_id: resourceId, - resource_type: 'file', - parent_resource_id: file.folder_resource_id, - owner_id: file.owner_id, - }, - ...(await folderLineage(db, file.folder_resource_id)), - ]; - } - return folderLineage(db, resourceId); -} - -async function folderLineage( - db: DbSession, - startResourceId: string, -): Promise { - const rows = await db.getAllAsync( - `WITH RECURSIVE lineage(resource_id, parent_resource_id, owner_id) AS ( - SELECT resource_id, parent_resource_id, owner_id FROM folders WHERE resource_id = ? - UNION ALL - SELECT f.resource_id, f.parent_resource_id, f.owner_id - FROM folders f - JOIN lineage l ON f.resource_id = l.parent_resource_id - ) - SELECT resource_id, 'folder' AS resource_type, parent_resource_id, owner_id FROM lineage`, - startResourceId, - ); - return rows; -} - -function isStale(cachedAt: number, now: number): boolean { - return now - cachedAt > PERMISSION_TTL_MS; -} - -function readOnlyAccess(permission: ResourcePermission, now: number): AccessLevel { - return isStale(permission.cachedAt, now) ? 'viewer' : permission.effectiveAccess; -} - -export async function canAccess( - resourceId: string, - resourceType: ResourceType, - required: AccessLevel, -): Promise { - const now = Date.now(); - // Le device est le propriétaire local de ses propres ressources (device_user_id - // seedé en owner_id, cf. v4). Owner = device-local : le check reste volontairement - // indépendant du compte connecté (fallback hors-cloud, décision offline). - const deviceUserId = await getDeviceUserId(); - - const exactCache = await getResourcePermission(resourceId, resourceType); - if (exactCache) { - if (exactCache.ownerId === deviceUserId) { - return { allowed: true, access: 'owner', source: 'owner', stale: false, expiresAt: null }; - } - if (exactCache.expiresAt != null && exactCache.expiresAt < now) { - return { - allowed: false, - access: exactCache.effectiveAccess, - source: 'cache', - stale: false, - expiresAt: exactCache.expiresAt, - }; - } - const stale = isStale(exactCache.cachedAt, now); - const applyAccess = readOnlyAccess(exactCache, now); - return { - allowed: rank(applyAccess) >= rank(required), - access: applyAccess, - source: 'cache', - stale, - expiresAt: exactCache.expiresAt, - }; - } - - const lineage = await resourceLineage(resourceId, resourceType); - let best: { - access: AccessLevel; - source: AccessSource; - stale: boolean; - expiresAt: number | null; - } | null = null; - - for (const [index, node] of lineage.entries()) { - if (node.owner_id === deviceUserId) { - return { allowed: true, access: 'owner', source: 'owner', stale: false, expiresAt: null }; - } - - const nodePermission = await getResourcePermission(node.resource_id, node.resource_type); - if (!nodePermission) continue; - if (nodePermission.expiresAt != null && nodePermission.expiresAt < now) continue; - if (index > 0 && nodePermission.inherit === false) continue; - - const stale = isStale(nodePermission.cachedAt, now); - const candidate = { - access: readOnlyAccess(nodePermission, now), - source: (index === 0 ? 'cache' : 'inherited') as AccessSource, - stale, - expiresAt: nodePermission.expiresAt, - }; - if ( - !best || - rank(candidate.access) > rank(best.access) || - (rank(candidate.access) === rank(best.access) && !candidate.stale && best.stale) - ) { - best = candidate; - } - } - - if (!best) { - return { allowed: false, access: null, source: 'none', stale: false, expiresAt: null }; - } - return { - allowed: rank(best.access) >= rank(required), - access: best.access, - source: best.source, - stale: best.stale, - expiresAt: best.expiresAt, - }; -} - -export async function canWrite( - resourceId: string, - resourceType: ResourceType, -): Promise { - return canAccess(resourceId, resourceType, 'editor'); -} - -export async function isOwner( - resourceId: string, - resourceType: ResourceType, -): Promise { - const check = await canAccess(resourceId, resourceType, 'owner'); - return check.allowed; -} - -function toResourcePermission(row: ResourcePermissionRow): ResourcePermission { - return { - resource_id: row.resource_id, - resourceType: row.resource_type, - effectiveAccess: row.effective_access, - inherit: row.inherit === 1, - ownerId: row.owner_id, - sharedById: row.shared_by_id, - expiresAt: row.expires_at, - cachedAt: row.cached_at, - updatedAt: row.updated_at, - }; -} \ No newline at end of file diff --git a/mobile/services/db/repositories/preferences.ts b/mobile/services/db/repositories/preferences.ts deleted file mode 100644 index a49b13c..0000000 --- a/mobile/services/db/repositories/preferences.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { getSession } from '../session'; -import { ACTIVE_USER_ID_KEY, AUTH_TOKEN_KEY, DEVICE_USER_ID_KEY, PREFERENCES_KEY } from '../schema'; -import type { UserPreferences } from '../types'; - -const DEFAULT_PREFERENCES: UserPreferences = { - syncMode: 'full', -}; - -export async function getDeviceUserId(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - DEVICE_USER_ID_KEY, - ); - if (row) return row.value; - - await db.runAsync( - `INSERT OR IGNORE INTO user_preferences ("key", "value", updated_at) - VALUES (?, lower(hex(randomblob(16))), ?)`, - DEVICE_USER_ID_KEY, - Date.now(), - ); - const seeded = await db.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - DEVICE_USER_ID_KEY, - ); - return seeded!.value; -} - -export async function saveUserPreferences(preferences: UserPreferences): Promise { - const db = await getSession(); - await db.runAsync( - `INSERT INTO user_preferences ("key", "value", updated_at) VALUES (?, ?, ?) - ON CONFLICT("key") DO UPDATE SET "value" = excluded."value", updated_at = excluded.updated_at`, - PREFERENCES_KEY, - JSON.stringify(preferences), - Date.now(), - ); -} - -export async function getDeviceAuthToken(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - AUTH_TOKEN_KEY, - ); - return row?.value ?? null; -} - -export async function saveDeviceAuthToken(token: string): Promise { - const db = await getSession(); - await db.runAsync( - `INSERT INTO user_preferences ("key", "value", updated_at) VALUES (?, ?, ?) - ON CONFLICT("key") DO UPDATE SET "value" = excluded."value", updated_at = excluded.updated_at`, - AUTH_TOKEN_KEY, - token, - Date.now(), - ); -} - -// Miroir non-sensible du compte connecté (le token vit en SecureStore). NULL -// = aucun compte actif (mode device-local legacy / tests). -export async function getActiveUserId(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - ACTIVE_USER_ID_KEY, - ); - return row?.value || null; -} - -export async function setActiveUserId(userId: string): Promise { - const db = await getSession(); - await db.runAsync( - `INSERT INTO user_preferences ("key", "value", updated_at) VALUES (?, ?, ?) - ON CONFLICT("key") DO UPDATE SET "value" = excluded."value", updated_at = excluded.updated_at`, - ACTIVE_USER_ID_KEY, - userId, - Date.now(), - ); -} - -export async function clearActiveUserId(): Promise { - const db = await getSession(); - await db.runAsync( - 'DELETE FROM user_preferences WHERE "key" = ?', - ACTIVE_USER_ID_KEY, - ); -} - -export async function getUserPreferences(): Promise { - const db = await getSession(); - const row = await db.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - PREFERENCES_KEY, - ); - if (!row) return DEFAULT_PREFERENCES; - - try { - return { ...DEFAULT_PREFERENCES, ...JSON.parse(row.value) } satisfies UserPreferences; - } catch { - return DEFAULT_PREFERENCES; - } -} \ No newline at end of file diff --git a/mobile/services/db/repositories/recipients.ts b/mobile/services/db/repositories/recipients.ts deleted file mode 100644 index 94c676d..0000000 --- a/mobile/services/db/repositories/recipients.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { getSession } from '../session'; -import type { Recipient, RecipientRow, RecipientType } from '../types'; - -export async function saveRecipient( - recipientType: RecipientType, - recipientId: string, - displayName: string, -): Promise { - const db = await getSession(); - await db.runAsync( - `INSERT INTO recipients (recipient_type, recipient_id, display_name, is_active, updated_at) - VALUES (?, ?, ?, 1, ?) - ON CONFLICT(recipient_id) DO UPDATE SET - recipient_type = excluded.recipient_type, - display_name = excluded.display_name, - is_active = 1, - updated_at = excluded.updated_at`, - recipientType, - recipientId, - displayName, - Date.now(), - ); - const row = await db.getFirstAsync( - 'SELECT * FROM recipients WHERE recipient_id = ?', - recipientId, - ); - return toRecipient(row!); -} - -export async function getRecipients(activeOnly = true): Promise { - const db = await getSession(); - const rows = activeOnly - ? await db.getAllAsync( - 'SELECT * FROM recipients WHERE is_active = 1 ORDER BY display_name ASC', - ) - : await db.getAllAsync('SELECT * FROM recipients ORDER BY display_name ASC'); - return rows.map(toRecipient); -} - -export async function setRecipientActive( - recipientType: RecipientType, - recipientId: string, - active: boolean, -): Promise { - const db = await getSession(); - await db.runAsync( - `UPDATE recipients SET is_active = ?, updated_at = ? - WHERE recipient_type = ? AND recipient_id = ?`, - active ? 1 : 0, - Date.now(), - recipientType, - recipientId, - ); -} - -function toRecipient(row: RecipientRow): Recipient { - return { - recipientType: row.recipient_type, - recipientId: row.recipient_id, - displayName: row.display_name, - isActive: row.is_active === 1, - updatedAt: row.updated_at, - }; -} \ No newline at end of file diff --git a/mobile/services/db/repositories/shareLinks.ts b/mobile/services/db/repositories/shareLinks.ts deleted file mode 100644 index 3ee3057..0000000 --- a/mobile/services/db/repositories/shareLinks.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { getSession } from '../session'; -import type { NewShareLink, ResourceType, ShareLink, ShareLinkRow } from '../types'; - -const PUSH_STATUS_SQL = `( - SELECT CASE - WHEN EXISTS ( - SELECT 1 FROM pending_operations p - WHERE p.ref_type = 'share_link' AND p.ref_id = sl.id AND p.status IN ('pending', 'in_progress') - ) THEN 'pending' - WHEN EXISTS ( - SELECT 1 FROM pending_operations p - WHERE p.ref_type = 'share_link' AND p.ref_id = sl.id AND p.status = 'failed' - ) THEN 'failed' - ELSE 'synced' - END -) AS push_status`; - -export async function createShareLink(input: NewShareLink): Promise { - const db = await getSession(); - const now = Date.now(); - - let token = input.token; - if (!token) { - const tokenRow = await db.getFirstAsync<{ token: string }>( - 'SELECT lower(hex(randomblob(16))) AS token', - ); - token = tokenRow!.token; - } - - const row = await db.getFirstAsync<{ id: number }>( - `INSERT INTO share_links - (token, resource_id, resource_type, has_password, allow_download, expires_at, max_downloads, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING id`, - token, - input.resourceId, - input.resourceType, - input.hasPassword ? 1 : 0, - input.allowDownload === false ? 0 : 1, - input.expiresAt ?? null, - input.maxDownloads ?? null, - now, - now, - ); - - const created = await getShareLinkById(row!.id); - if (!created) throw new Error('share link insert failed'); - return created; -} - -export async function getShareLinkById(id: number): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT sl.*, ${PUSH_STATUS_SQL} FROM share_links sl WHERE sl.id = ?`, - id, - ); - return row ? toShareLink(row) : null; -} - -export async function getShareLinkByToken(token: string): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT sl.*, ${PUSH_STATUS_SQL} FROM share_links sl WHERE sl.token = ?`, - token, - ); - return row ? toShareLink(row) : null; -} - -export async function getShareLinks( - resourceId?: string, - resourceType?: ResourceType, -): Promise { - const db = await getSession(); - let sql = `SELECT sl.*, ${PUSH_STATUS_SQL} FROM share_links sl`; - const params: string[] = []; - if (resourceId) { - sql += ' WHERE sl.resource_id = ?'; - params.push(resourceId); - if (resourceType) { - sql += ' AND sl.resource_type = ?'; - params.push(resourceType); - } - } - sql += ' ORDER BY sl.created_at ASC'; - const rows = await db.getAllAsync(sql, ...params); - return rows.map(toShareLink); -} - -export async function incrementLinkDownloads(id: number): Promise { - const db = await getSession(); - await db.runAsync( - `UPDATE share_links SET downloads_count = downloads_count + 1, updated_at = ? WHERE id = ?`, - Date.now(), - id, - ); -} - -export async function revokeShareLink(id: number): Promise { - const db = await getSession(); - await db.runAsync( - `UPDATE share_links SET is_revoked = 1, updated_at = ? WHERE id = ?`, - Date.now(), - id, - ); -} - -export async function removeShareLinksForResource( - resourceId: string, - resourceType: ResourceType, -): Promise { - const db = await getSession(); - await db.runAsync('DELETE FROM share_links WHERE resource_id = ? AND resource_type = ?', resourceId, resourceType); -} - -function toShareLink(row: ShareLinkRow & { push_status: ShareLink['pushStatus'] }): ShareLink { - return { - id: row.id, - token: row.token, - resourceId: row.resource_id, - resourceType: row.resource_type, - hasPassword: row.has_password === 1, - allowDownload: row.allow_download === 1, - expiresAt: row.expires_at, - maxDownloads: row.max_downloads, - downloadsCount: row.downloads_count, - isRevoked: row.is_revoked === 1, - createdAt: row.created_at, - updatedAt: row.updated_at, - pushStatus: row.push_status, - }; -} \ No newline at end of file diff --git a/mobile/services/db/repositories/shares.ts b/mobile/services/db/repositories/shares.ts deleted file mode 100644 index af2ce2b..0000000 --- a/mobile/services/db/repositories/shares.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { getSession } from '../session'; -import type { NewShare, ResourceType, Share, ShareRow } from '../types'; - -const PUSH_STATUS_SQL = `( - SELECT CASE - WHEN EXISTS ( - SELECT 1 FROM pending_operations p - WHERE p.ref_type = 'share' AND p.ref_id = s.id AND p.status IN ('pending', 'in_progress') - ) THEN 'pending' - WHEN EXISTS ( - SELECT 1 FROM pending_operations p - WHERE p.ref_type = 'share' AND p.ref_id = s.id AND p.status = 'failed' - ) THEN 'failed' - ELSE 'synced' - END -) AS push_status`; - -export async function saveShare(share: NewShare): Promise { - const db = await getSession(); - const now = Date.now(); - - await db.runAsync( - `INSERT INTO shares - (resource_id, resource_type, recipient_type, recipient_id, relation, inherit, expires_at, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(resource_id, resource_type, recipient_type, recipient_id) DO UPDATE SET - relation = excluded.relation, - inherit = excluded.inherit, - expires_at = excluded.expires_at, - updated_at = excluded.updated_at`, - share.resourceId, - share.resourceType, - share.recipientType, - share.recipientId, - share.relation, - share.inherit === false ? 0 : 1, - share.expiresAt ?? null, - now, - now, - ); - - const row = await db.getFirstAsync( - `SELECT s.*, ${PUSH_STATUS_SQL} FROM shares s - WHERE s.resource_id = ? AND s.resource_type = ? AND s.recipient_type = ? AND s.recipient_id = ?`, - share.resourceId, - share.resourceType, - share.recipientType, - share.recipientId, - ); - return toShare(row!); -} - -export async function getShare( - resourceId: string, - resourceType: ResourceType, - recipientType: 'user' | 'group', - recipientId: string, -): Promise { - const db = await getSession(); - const row = await db.getFirstAsync( - `SELECT s.*, ${PUSH_STATUS_SQL} FROM shares s - WHERE s.resource_id = ? AND s.resource_type = ? AND s.recipient_type = ? AND s.recipient_id = ?`, - resourceId, - resourceType, - recipientType, - recipientId, - ); - return row ? toShare(row) : null; -} - -export async function getShares( - resourceId?: string, - resourceType?: ResourceType, -): Promise { - const db = await getSession(); - let sql = `SELECT s.*, ${PUSH_STATUS_SQL} FROM shares s`; - const params: string[] = []; - if (resourceId) { - sql += ' WHERE s.resource_id = ?'; - params.push(resourceId); - if (resourceType) { - sql += ' AND s.resource_type = ?'; - params.push(resourceType); - } - } - sql += ' ORDER BY s.created_at ASC'; - const rows = await db.getAllAsync(sql, ...params); - return rows.map(toShare); -} - -export async function removeShare( - resourceId: string, - resourceType: ResourceType, - recipientType: 'user' | 'group', - recipientId: string, -): Promise { - const db = await getSession(); - await db.runAsync( - `DELETE FROM shares - WHERE resource_id = ? AND resource_type = ? AND recipient_type = ? AND recipient_id = ?`, - resourceId, - resourceType, - recipientType, - recipientId, - ); -} - -export async function removeSharesForResource( - resourceId: string, - resourceType: ResourceType, -): Promise { - const db = await getSession(); - await db.runAsync('DELETE FROM shares WHERE resource_id = ? AND resource_type = ?', resourceId, resourceType); -} - -function toShare(row: ShareRow & { push_status: Share['pushStatus'] }): Share { - return { - id: row.id, - resourceId: row.resource_id, - resourceType: row.resource_type, - recipientType: row.recipient_type, - recipientId: row.recipient_id, - relation: row.relation, - inherit: row.inherit === 1, - expiresAt: row.expires_at, - createdAt: row.created_at, - updatedAt: row.updated_at, - pushStatus: row.push_status, - }; -} \ No newline at end of file diff --git a/mobile/services/db/schema.ts b/mobile/services/db/schema.ts deleted file mode 100644 index 880ceec..0000000 --- a/mobile/services/db/schema.ts +++ /dev/null @@ -1,49 +0,0 @@ -export const DATABASE_NAME = 'dot.db'; - -export const DATABASE_VERSION = 5; - -export const PREFERENCES_KEY = 'user_preferences'; - -export const DEVICE_USER_ID_KEY = 'device_user_id'; - -export const AUTH_TOKEN_KEY = 'auth_token'; - -// Miroir non-sensible du compte connecté (voir services/secureStore.ts) : le -// token lui-même reste en SecureStore ; seul l'id du compte actif est répété en -// SQLite pour permettre aux repositories de scoper leurs lectures/écritures -// sans avoir à importer expo-secure-store (tests Node inclus). -export const ACTIVE_USER_ID_KEY = 'active_user_id'; - -export const FOLDER_COLUMNS = [ - 'resource_id', - 'uri', - 'name', - '"exists"', - 'sync_status', - 'added_at', - 'updated_at', - 'parent_resource_id', - 'owner_id', -] as const; - -export const FOLDER_COLUMNS_SQL = FOLDER_COLUMNS.join(', '); - -export const FILE_COLUMNS = [ - 'resource_id', - 'uri', - 'name', - 'folder_resource_id', - 'extension', - 'size', - '"type"', - '"exists"', - 'last_modified', - 'sync_status', - 'added_at', - 'updated_at', - 'owner_id', -] as const; - -export const FILE_COLUMNS_SQL = FILE_COLUMNS.join(', '); - -export const PERMISSION_TTL_MS = 24 * 60 * 60 * 1000; \ No newline at end of file diff --git a/mobile/services/db/session.ts b/mobile/services/db/session.ts deleted file mode 100644 index 1aeb050..0000000 --- a/mobile/services/db/session.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getDatabase, withDatabaseRetry } from './client'; -import type { SQLiteBindValue } from 'expo-sqlite'; - -export type DbSession = { - runAsync(sql: string, ...params: SQLiteBindValue[]): Promise; - getFirstAsync(sql: string, ...params: SQLiteBindValue[]): Promise; - getAllAsync(sql: string, ...params: SQLiteBindValue[]): Promise; -}; - -let override: DbSession | null = null; - -export function __setDbForTests(db: DbSession | null): void { - override = db; -} - -export async function getSession(): Promise { - if (override) return override; - await getDatabase(); - return liveSession; -} - -const liveSession: DbSession = { - runAsync: (sql, ...params) => - withDatabaseRetry((db) => db.runAsync(sql, ...params)), - getFirstAsync: (sql, ...params) => - withDatabaseRetry((db) => db.getFirstAsync(sql, ...params)), - getAllAsync: (sql, ...params) => - withDatabaseRetry((db) => db.getAllAsync(sql, ...params)), -}; \ No newline at end of file diff --git a/mobile/services/db/transitions.ts b/mobile/services/db/transitions.ts deleted file mode 100644 index 9616294..0000000 --- a/mobile/services/db/transitions.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { SyncStatus } from './types'; - -export type SyncEvent = - | 'created_local' - | 'uploaded' - | 'downloaded' - | 'deleted_cloud'; - -export type SyncTransitionResult = SyncStatus | 'gone'; - -export function transitionSyncStatus( - from: SyncStatus, - event: SyncEvent, -): SyncTransitionResult { - switch (from) { - case 'local': - switch (event) { - case 'created_local': - return 'local'; - case 'uploaded': - return 'cloud'; - case 'downloaded': - return 'local-cloud'; - case 'deleted_cloud': - return 'gone'; - } - case 'cloud': - switch (event) { - case 'created_local': - return 'local-cloud'; - case 'uploaded': - return 'cloud'; - case 'downloaded': - return 'local-cloud'; - case 'deleted_cloud': - return 'gone'; - } - case 'local-cloud': - switch (event) { - case 'created_local': - case 'uploaded': - case 'downloaded': - return 'local-cloud'; - case 'deleted_cloud': - return 'local'; - } - } -} \ No newline at end of file diff --git a/mobile/services/db/types.ts b/mobile/services/db/types.ts deleted file mode 100644 index 374fc91..0000000 --- a/mobile/services/db/types.ts +++ /dev/null @@ -1,266 +0,0 @@ -import type { FileEntry, Folder } from '../safDirectory.types'; - -export type { FileEntry, Folder } from '../safDirectory.types'; - -export type SyncStatus = 'local' | 'cloud' | 'local-cloud'; - -export type AccessLevel = 'owner' | 'editor' | 'commenter' | 'viewer'; - -export type ResourceType = 'folder' | 'file'; - -export type RecipientType = 'user' | 'group'; - -export type PushStatus = 'pending' | 'synced' | 'failed'; - -export type UserPreferences = { - syncMode: 'full' | 'manual' | 'none'; -}; - -export type FolderRow = { - id: number; - resource_id: string; - uri: string | null; - name: string; - exists: number | null; - parent_resource_id: string | null; - owner_id: string; - sync_status: SyncStatus; - added_at: number; - updated_at: number; -}; - -export type FileRow = { - id: number; - resource_id: string; - uri: string | null; - name: string; - folder_resource_id: string; - extension: string | null; - size: number; - type: string | null; - exists: number; - last_modified: number | null; - owner_id: string; - sync_status: SyncStatus; - added_at: number; - updated_at: number; -}; - -export type StoredFolder = { - resource_id: string; - uri: string | null; - name: string; - exists: boolean; - parent_resource_id: string | null; - owner_id: string; - syncStatus: SyncStatus; - addedAt: number; - updatedAt: number; -}; - -export type StoredFile = { - resource_id: string; - uri: string | null; - name: string; - folder_resource_id: string; - extension: string; - exists: boolean; - size: number; - type: string; - lastModified: number | null; - owner_id: string; - syncStatus: SyncStatus; - addedAt: number; - updatedAt: number; -}; - -export type ResourcePermissionRow = { - id: number; - resource_id: string; - resource_type: ResourceType; - effective_access: AccessLevel; - inherit: number; - owner_id: string | null; - shared_by_id: string | null; - expires_at: number | null; - cached_at: number; - updated_at: number; -}; - -export type ResourcePermission = { - resource_id: string; - resourceType: ResourceType; - effectiveAccess: AccessLevel; - inherit: boolean; - ownerId: string | null; - sharedById: string | null; - expiresAt: number | null; - cachedAt: number; - updatedAt: number; -}; - -export type NewResourcePermission = { - resource_id: string; - resourceType: ResourceType; - effectiveAccess: AccessLevel; - inherit?: boolean; - ownerId?: string | null; - sharedById?: string | null; - expiresAt?: number | null; -}; - -export type ShareRow = { - id: number; - resource_id: string; - resource_type: ResourceType; - recipient_type: RecipientType; - recipient_id: string; - relation: AccessLevel; - inherit: number; - expires_at: number | null; - created_at: number; - updated_at: number; -}; - -export type Share = { - id: number; - resourceId: string; - resourceType: ResourceType; - recipientType: RecipientType; - recipientId: string; - relation: AccessLevel; - inherit: boolean; - expiresAt: number | null; - createdAt: number; - updatedAt: number; - pushStatus: PushStatus; -}; - -export type NewShare = { - resourceId: string; - resourceType: ResourceType; - recipientType: RecipientType; - recipientId: string; - relation: AccessLevel; - inherit?: boolean; - expiresAt?: number | null; -}; - -export type RecipientRow = { - id: number; - recipient_type: RecipientType; - recipient_id: string; - display_name: string; - is_active: number; - updated_at: number; -}; - -export type Recipient = { - recipientType: RecipientType; - recipientId: string; - displayName: string; - isActive: boolean; - updatedAt: number; -}; - -export type ShareLinkRow = { - id: number; - token: string; - resource_id: string; - resource_type: ResourceType; - has_password: number; - allow_download: number; - expires_at: number | null; - max_downloads: number | null; - downloads_count: number; - is_revoked: number; - created_at: number; - updated_at: number; -}; - -export type ShareLink = { - id: number; - token: string; - resourceId: string; - resourceType: ResourceType; - hasPassword: boolean; - allowDownload: boolean; - expiresAt: number | null; - maxDownloads: number | null; - downloadsCount: number; - isRevoked: boolean; - createdAt: number; - updatedAt: number; - pushStatus: PushStatus; -}; - -export type NewShareLink = { - token?: string; - resourceId: string; - resourceType: ResourceType; - hasPassword?: boolean; - allowDownload?: boolean; - expiresAt?: number | null; - maxDownloads?: number | null; -}; - -export type PendingOperationStatus = - | 'pending' - | 'in_progress' - | 'completed' - | 'failed' - | 'cancelled'; - -export type PendingOperationType = - | 'create_resource' - | 'update_metadata' - | 'delete_resource' - | 'move_resource' - | 'share' - | 'revoke_share' - | 'update_share' - | 'create_link' - | 'revoke_link'; - -export type PendingOperationRefType = 'resource' | 'share' | 'share_link'; - -export type PendingOperationRow = { - id: number; - resource_id: string | null; - resource_type: ResourceType | null; - ref_type: PendingOperationRefType | null; - ref_id: number | null; - operation: PendingOperationType; - payload: string; - status: PendingOperationStatus; - attempts: number; - error: string | null; - created_at: number; - next_retry_at: number | null; - last_error_at: number | null; -}; - -export type PendingOperation = { - id: number; - resourceId: string | null; - resourceType: ResourceType | null; - refType: PendingOperationRefType | null; - refId: number | null; - operation: PendingOperationType; - payload: unknown; - status: PendingOperationStatus; - attempts: number; - error: string | null; - createdAt: number; - nextRetryAt: number | null; - lastErrorAt: number | null; -}; - -export type NewPendingOperation = { - resourceId?: string | null; - resourceType?: ResourceType | null; - refType?: PendingOperationRefType | null; - refId?: number | null; - operation: PendingOperationType; - payload?: unknown; -}; \ No newline at end of file diff --git a/mobile/services/localStorage.ts b/mobile/services/localStorage.ts deleted file mode 100644 index 4772a9d..0000000 --- a/mobile/services/localStorage.ts +++ /dev/null @@ -1,66 +0,0 @@ -export { - getUserPreferences, - getDeviceUserId, - getDeviceAuthToken, - saveDeviceAuthToken, - getActiveUserId, - setActiveUserId, - clearActiveUserId, - getFolders, - getFolderFolders, - getFolder, - getFiles, - getFile, - removeFolder, - removeFile, - saveFile, - saveFolder, - saveDirectory, - saveUserPreferences, - getResourcePermission, - saveResourcePermission, - canAccess, - canWrite, - isOwner, - saveShare, - getShare, - getShares, - removeShare, - removeSharesForResource, - createShareLink, - getShareLinkById, - getShareLinkByToken, - getShareLinks, - incrementLinkDownloads, - revokeShareLink, - removeShareLinksForResource, - saveRecipient, - getRecipients, - setRecipientActive, - enqueuePendingOperation, - getPendingOperations, - getNextQueuedOperation, - markPendingOperation, - transitionSyncStatus, -} from './db'; - -export type { - AccessLevel, - AccessCheck, - AccessSource, - StoredFile, - StoredFolder, - SyncStatus, - PushStatus, - UserPreferences, - ResourceType, - ResourcePermission, - RecipientType, - Recipient, - Share, - ShareLink, - PendingOperation, - PendingOperationStatus, - SyncEvent, - SyncTransitionResult, -} from './db'; \ No newline at end of file diff --git a/mobile/services/safDirectory.ts b/mobile/services/safDirectory.ts deleted file mode 100644 index 7228ce1..0000000 --- a/mobile/services/safDirectory.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Directory, File, Paths } from 'expo-file-system'; -import type { - DirectoryEntry, - FileEntry, - Folder, - FolderInfo, -} from './safDirectory.types'; -import { __setSafWalkImpl, yieldToMainThread } from './safWalk'; -export { listFoldersChunked, yieldToMainThread, DEFAULT_WALK_BUDGET_MS } from './safWalk'; - -// Bootstrap: wire the real SAF implementation for the Expo runtime. -__setSafWalkImpl({ - list: listDirectoryEntries, - info: getFolderInfo, - yield: yieldToMainThread, -}); - -export async function pickDirectory(initialUri?: string): Promise { - try { - const directory = await Directory.pickDirectoryAsync(initialUri); - if (!directory || !directory.uri) return null; - return toFolder({ uri: directory.uri, name: directory.name, exists: directory.exists }); - } catch (error) { - console.warn('[safDirectory] pickDirectory failed:', error); - return null; - } -} - -export function listDirectoryEntries(directoryUri: string): DirectoryEntry[] { - const directory = new Directory(directoryUri); - if (!directory.exists) return []; - try { - return directory.list().map(toEntry); - } catch (error) { - console.warn('[safDirectory] listDirectoryEntries failed:', error); - return []; - } -} - -export function getFolderInfo(directoryUri: string): FolderInfo { - const directory = new Directory(directoryUri); - return { - uri: directory.uri, - name: directory.name, - exists: directory.exists, - }; -} - -/** Creates a directory. Intentionally lets exceptions propagate (permissions, quota, etc). */ -export function createDirectory(directoryUri: string, name: string): Folder { - const directory = new Directory(Paths.join(directoryUri, name)); - directory.create({ idempotent: true, intermediates: true }); - return toFolder({ uri: directory.uri, name: directory.name, exists: directory.exists }); -} - -export function fileFromUri(uri: string): FileEntry { - return toFileEntry(new File(uri)); -} - -function toFileEntry(file: File): FileEntry { - return { - uri: file.uri, - name: file.name, - isDirectory: false, - extension: file.extension, - exists: file.exists, - size: file.size, - type: file.type, - lastModified: file.lastModified, - }; -} - -function toEntry(item: Directory | File): DirectoryEntry { - return item instanceof Directory - ? toFolder({ uri: item.uri, name: item.name, exists: item.exists }) - : toFileEntry(item); -} - -// Perf: avoids constructing a Directory when all fields are already provided by the caller. -function toFolder({ uri, name, exists }: { uri: string; name?: string; exists?: boolean }): Folder { - const directory = exists === undefined ? new Directory(uri) : null; - return { - uri, - name: name ?? directory?.name ?? Paths.basename(uri), - isDirectory: true, - exists: exists ?? directory?.exists, - }; -} diff --git a/mobile/services/safDirectory.types.ts b/mobile/services/safDirectory.types.ts deleted file mode 100644 index f7d718d..0000000 --- a/mobile/services/safDirectory.types.ts +++ /dev/null @@ -1,31 +0,0 @@ -export type Folder = { - uri: string; - name: string; - isDirectory: true; - /** `undefined` when neither caller nor Directory fallback provided a value. */ - exists?: boolean; -}; - -export type FileEntry = { - uri: string; - name: string; - isDirectory: false; - extension: string; - exists: boolean; - size: number; - type: string; - lastModified: number | null; -}; - -export type DirectoryEntry = Folder | FileEntry; - -export type FolderInfo = { - uri: string; - name: string; - exists: boolean; -}; - -export type PickDirectoryOptions = { - recursive?: boolean; - includeRoot?: boolean; -}; \ No newline at end of file diff --git a/mobile/services/safWalk.ts b/mobile/services/safWalk.ts deleted file mode 100644 index 3275116..0000000 --- a/mobile/services/safWalk.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { - DirectoryEntry, - Folder, - FolderInfo, - PickDirectoryOptions, -} from './safDirectory.types'; - -export async function yieldToMainThread(): Promise { - await new Promise((resolve) => setTimeout(resolve, 0)); -} - -export const DEFAULT_WALK_BUDGET_MS = 16; - -export type SafWalkImpl = { - list: (uri: string) => DirectoryEntry[]; - info: (uri: string) => FolderInfo; - yield: () => Promise; -}; - -let walkImpl: SafWalkImpl | null = null; - -export function __setSafWalkImpl(impl: SafWalkImpl): void { - walkImpl = impl; -} - -export function __setSafWalkForTests(impl: Partial): () => void { - const previous = walkImpl; - walkImpl = { ...previous, ...impl } as SafWalkImpl; - return () => { - walkImpl = previous; - }; -} - -export async function listFoldersChunked( - directoryUri: string, - options: PickDirectoryOptions = {}, - budgetMs = DEFAULT_WALK_BUDGET_MS, -): Promise { - const impl = walkImpl; - if (!impl) throw new Error('SafWalkImpl not configured'); - - const { recursive = false, includeRoot = false } = options; - const result: Folder[] = []; - if (includeRoot) { - result.push({ ...impl.info(directoryUri), isDirectory: true }); - } - if (!recursive) { - for (const entry of impl.list(directoryUri)) { - if (entry.isDirectory) result.push(entry); - } - return result; - } - - let lastYield = Date.now(); - if (Date.now() - lastYield >= budgetMs) { - lastYield = Date.now(); - await impl.yield(); - } - // Stack of Folders identified by uri; push in reverse so the first-listed - // descendant is popped first, preserving the DFS pre-order of the recursive - // traversal (a folder's subtree is fully explored before its later siblings). - const stack: Folder[] = impl - .list(directoryUri) - .filter((entry): entry is Folder => entry.isDirectory) - .reverse(); - while (stack.length > 0) { - if (Date.now() - lastYield >= budgetMs) { - lastYield = Date.now(); - await impl.yield(); - } - const folder = stack.pop()!; - result.push(folder); - const children = impl - .list(folder.uri) - .filter((entry): entry is Folder => entry.isDirectory); - for (let i = children.length - 1; i >= 0; i--) { - stack.push(children[i]); - } - } - return result; -} - -export function listEntries(directoryUri: string): DirectoryEntry[] { - const impl = walkImpl; - if (!impl) throw new Error('SafWalkImpl not configured'); - return impl.list(directoryUri); -} \ No newline at end of file diff --git a/mobile/services/secureStore.ts b/mobile/services/secureStore.ts deleted file mode 100644 index b3ae029..0000000 --- a/mobile/services/secureStore.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as SecureStore from 'expo-secure-store'; -import type { User } from '../api/types'; - -// Clés SecureStore (iOS keychain / Android Keystore). Le token et le profil du -// compte connecté ne sont JAMAIS persistés en SQLite : un miroir non-sensible -// (active_user_id) est répété en base pour le scoping des repositories. -const TOKEN_KEY = 'vaultdrop.auth_token'; -const ACCOUNT_KEY = 'vaultdrop.account'; - -export async function getStoredToken(): Promise { - try { - return await SecureStore.getItemAsync(TOKEN_KEY); - } catch { - return null; - } -} - -export async function getStoredAccount(): Promise { - try { - const raw = await SecureStore.getItemAsync(ACCOUNT_KEY); - if (!raw) return null; - const parsed = JSON.parse(raw) as User; - if (typeof parsed?.id !== 'string' || typeof parsed?.username !== 'string') return null; - return parsed; - } catch { - return null; - } -} - -export async function setStoredSession(token: string, user: User): Promise { - await SecureStore.setItemAsync(TOKEN_KEY, token); - await SecureStore.setItemAsync(ACCOUNT_KEY, JSON.stringify(user)); -} - -export async function clearStoredSession(): Promise { - try { - await SecureStore.deleteItemAsync(TOKEN_KEY); - await SecureStore.deleteItemAsync(ACCOUNT_KEY); - } catch { - // clés absentes → rien à supprimer - } -} \ No newline at end of file diff --git a/mobile/tests/apiClient.test.ts b/mobile/tests/apiClient.test.ts deleted file mode 100644 index c670985..0000000 --- a/mobile/tests/apiClient.test.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { test, beforeEach, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import { api, setAuthToken, setUnauthorizedHandler, ApiError } from '../api/client'; -import type { ApiData } from '../api/types'; - -function jsonResponse(status: number, body: unknown, headers?: HeadersInit): Response { - return new Response(typeof body === 'string' ? body : JSON.stringify(body), { - status, - headers: { 'Content-Type': 'application/json', ...headers }, - }); -} - -function ok(data: unknown, meta?: unknown): Response { - return jsonResponse(200, { data, ...(meta !== undefined ? { meta } : {}) }); -} - -let calls: { url: string; init: RequestInit }[] = []; -const originalFetch = globalThis.fetch; - -function stubFetch(handler: (url: string, init: RequestInit) => Promise | Response): void { - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { - calls.push({ url: String(input), init: init ?? {} }); - return handler(String(input), init ?? {}); - }; -} - -// URL absolue → chemin + query seulement (le prefix est EXPO_PUBLIC_API_BASE_URL) -function pathOf(url: string): string { - const u = new URL(url); - return u.pathname + u.search; -} - -beforeEach(() => { - calls = []; - setAuthToken(null); - setUnauthorizedHandler(null); -}); - -afterEach(() => { - globalThis.fetch = originalFetch; - setUnauthorizedHandler(null); -}); - -test('nominal : GET /health', async () => { - stubFetch(() => ok({ status: 'healthy' })); - const res = await api.health(); - assert.equal(pathOf(calls[0].url), '/api/v1/health'); - assert.equal(calls[0].init.method ?? 'GET', 'GET'); - assert.deepEqual(res.data, { status: 'healthy' }); -}); - -test('nominal : POST /devices (register) — plus aucun token, device-only', async () => { - stubFetch(() => ok({ deviceId: 'aaaa' })); - const res = await api.registerDevice('aaaa'); - assert.equal(pathOf(calls[0].url), '/api/v1/devices'); - assert.equal(calls[0].init.method, 'POST'); - assert.deepEqual(JSON.parse(String(calls[0].init.body)), { deviceId: 'aaaa' }); - assert.deepEqual(res.data, { deviceId: 'aaaa' }); - assert.ok(!('token' in res.data), 'POST /devices ne doit plus émettre de token'); -}); - -test('nominal : POST /auth/login (seule porte de token)', async () => { - stubFetch(() => - ok({ - token: 'v4.local.xyz', - expires_at: 1700000000000, - user: { id: 'u-1', username: 'alice', is_admin: false }, - }), - ); - const res = await api.login('alice', 'secret-pass', 'dev-1'); - assert.equal(pathOf(calls[0].url), '/api/v1/auth/login'); - assert.equal(calls[0].init.method, 'POST'); - assert.deepEqual(JSON.parse(String(calls[0].init.body)), { - username: 'alice', - password: 'secret-pass', - device_id: 'dev-1', - }); - assert.equal(res.data.user.id, 'u-1'); - assert.equal(res.data.user.is_admin, false); -}); - -test('auth : 401 de login ne déclenche PAS le handler de purge de session', async () => { - let purged = 0; - setUnauthorizedHandler(() => { - purged++; - }); - stubFetch(() => jsonResponse(401, { error: { code: 'UNAUTHORIZED', message: 'invalid credentials' } })); - await assert.rejects(() => api.login('bob', 'wrong', 'dev-1'), (err: unknown) => (err as ApiError).code === 'UNAUTHORIZED'); - assert.equal(purged, 0, 'mauvais identifiants ≠ session à purger'); -}); - -test('auth : 401 d’un endpoint protégé déclenche le handler de purge', async () => { - let purged = 0; - setUnauthorizedHandler(() => { - purged++; - }); - setAuthToken('tok-expired'); - stubFetch(() => jsonResponse(401, { error: { code: 'UNAUTHORIZED', message: 'token invalid' } })); - await assert.rejects(() => api.listFiles(), (err: unknown) => (err as ApiError).code === 'UNAUTHORIZED'); - assert.equal(purged, 1, 'token révoqué → purge de session'); - setUnauthorizedHandler(null); -}); - -test('nominal : PATCH /users/me/password', async () => { - stubFetch(() => ok({ id: 'u-1' })); - await api.changePassword('old-pass', 'new-pass-8chars'); - assert.equal(pathOf(calls[0].url), '/api/v1/users/me/password'); - assert.equal(calls[0].init.method, 'PATCH'); - assert.deepEqual(JSON.parse(String(calls[0].init.body)), { - current_password: 'old-pass', - new_password: 'new-pass-8chars', - }); -}); - -test('nominal : GET /users/resolve?username= (résolution exacte)', async () => { - stubFetch(() => ok({ id: 'u-9', username: 'bob' })); - const res = await api.resolveUser('BOB'); - assert.equal(pathOf(calls[0].url), '/api/v1/users/resolve?username=BOB'); - assert.equal(res.data.username, 'bob'); -}); - -test('nominal : GET /files — query filtrée (undefined/null ignorés)', async () => { - stubFetch(() => ok([], { page: 1, pageSize: 50, total: 0 })); - const res = await api.listFiles({ folderId: 'abc', page: 2, pageSize: 50, sort: undefined }); - assert.equal(pathOf(calls[0].url), '/api/v1/files?folderId=abc&page=2&pageSize=50'); - assert.equal(calls[0].init.method ?? 'GET', 'GET'); - assert.deepEqual(res.data, []); - assert.equal(res.meta?.total, 0); -}); - -test('nominal : GET/DELETE /files/:id — id encodé', async () => { - stubFetch(() => ok({ id: 'a%2Fb' })); - await api.getFile('a/b'); - await api.deleteFile('a/b'); - assert.equal(pathOf(calls[0].url), '/api/v1/files/a%2Fb'); - assert.equal(pathOf(calls[1].url), '/api/v1/files/a%2Fb'); - assert.equal(calls[1].init.method, 'DELETE'); -}); - -test('nominal : GET /files/search?q=', async () => { - stubFetch(() => ok([])); - await api.searchFiles('rapport', { page: 1 }); - assert.equal(pathOf(calls[0].url), '/api/v1/files/search?q=rapport&page=1'); -}); - -test('nominal : GET /files/folders', async () => { - stubFetch(() => ok([{ id: 'f', name: 'Docs' }])); - const res = await api.listFolders(); - assert.equal(pathOf(calls[0].url), '/api/v1/files/folders'); - assert.equal(res.data[0].name, 'Docs'); -}); - -test('nominal : POST /files/upload — multipart FormData, pas de Content-Type manuel', async () => { - stubFetch(() => ok({ id: 'u', name: 'a.txt', size: 3 })); - await api.uploadFile({ uri: 'file:///a.txt', name: 'a.txt', mimeType: 'text/plain' }, 'folder1'); - assert.equal(pathOf(calls[0].url), '/api/v1/files/upload'); - assert.equal(calls[0].init.method, 'POST'); - const form = calls[0].init.body as FormData; - assert.ok(form instanceof FormData); - // RN transforme le pseudo-objet {uri,name,type} en part de fichier ; sous - // Node la célébration est stringifiée — on vérifie juste la présence. - assert.notEqual(form.get('file'), null, 'la part "file" doit être présente'); - assert.equal(form.get('folderId'), 'folder1'); - assert.equal(calls[0].init.headers, undefined, 'le client ne doit jamais fixer Content-Type'); -}); - -test('nominal : OCR create + get', async () => { - stubFetch(() => ok({ id: 'j', status: 'queued' })); - const created = await api.createOcrJob('file-1'); - assert.equal(pathOf(calls[0].url), '/api/v1/ocr/jobs'); - assert.equal(calls[0].init.method, 'POST'); - assert.deepEqual(JSON.parse(String(calls[0].init.body)), { fileId: 'file-1' }); - await api.getOcrJob('j'); - assert.equal(pathOf(calls[1].url), '/api/v1/ocr/jobs/j'); - assert.equal(created.data.status, 'queued'); -}); - -test('enveloppe : { data, meta } parsée intégralement', async () => { - stubFetch(() => ok({ id: 'x', name: 'n' }, { page: 1, pageSize: 10, total: 3 })); - const res: ApiData<{ id: string }> = await api.getFile('x'); - assert.deepEqual(res, { data: { id: 'x', name: 'n' }, meta: { page: 1, pageSize: 10, total: 3 } }); -}); - -test('hostile : 200 + HTML (proxy) → INVALID_RESPONSE', async () => { - stubFetch(() => new Response('Bad Gateway', { status: 200, headers: { 'Content-Type': 'text/html' } })); - await assert.rejects(() => api.health(), (err: unknown) => { - assert.ok(err instanceof ApiError); - assert.equal((err as ApiError).code, 'INVALID_RESPONSE'); - return true; - }); -}); - -test('hostile : 200 + corps vide → INVALID_RESPONSE', async () => { - stubFetch(() => new Response(null, { status: 200 })); - await assert.rejects(() => api.health(), (err: unknown) => (err as ApiError).code === 'INVALID_RESPONSE'); -}); - -test('hostile : 200 + JSON sans clé data → INVALID_RESPONSE', async () => { - stubFetch(() => jsonResponse(200, { foo: 1 })); - await assert.rejects(() => api.health(), (err: unknown) => (err as ApiError).code === 'INVALID_RESPONSE'); -}); - -test('hostile : 200 + enveloppe {error} → INVALID_RESPONSE', async () => { - stubFetch(() => jsonResponse(200, { error: { code: 'X', message: 'm' } })); - await assert.rejects(() => api.health(), (err: unknown) => (err as ApiError).code === 'INVALID_RESPONSE'); -}); - -test('liste brute : 200 + [] → data tolérée', async () => { - stubFetch(() => jsonResponse(200, [])); - const res = await api.listFolders(); - assert.deepEqual(res, { data: [] }); -}); - -test('erreur enveloppée : 400 {error.code} → code transmis', async () => { - stubFetch(() => jsonResponse(400, { error: { code: 'INVALID_DEVICE_ID', message: 'bad' } })); - await assert.rejects(() => api.registerDevice('zz'), (err: unknown) => { - const e = err as ApiError; - return e.code === 'INVALID_DEVICE_ID' && e.message === 'bad'; - }); -}); - -test('erreur nue : 502 HTML → HTTP_502', async () => { - stubFetch(() => new Response('502', { status: 502, headers: { 'Content-Type': 'text/html' } })); - await assert.rejects(() => api.health(), (err: unknown) => (err as ApiError).code === 'HTTP_502'); -}); - -test('réseau : fetch rejette (dont timeout/AbortError) → NETWORK_ERROR', async () => { - stubFetch(() => { - throw new DOMException('The operation was aborted.', 'AbortError'); - }); - await assert.rejects(() => api.health(), (err: unknown) => (err as ApiError).code === 'NETWORK_ERROR'); -}); - -test('auth : sans token, pas de header Authorization', async () => { - stubFetch(() => ok([])); - await api.listFiles(); - assert.equal(calls[0].init.headers, undefined); -}); - -test('auth : token posé → Authorization: Bearer', async () => { - setAuthToken('tok-123'); - stubFetch(() => ok([])); - await api.listFiles(); - const headers = new Headers(calls[0].init.headers); - assert.equal(headers.get('Authorization'), 'Bearer tok-123'); -}); - -test('auth : fusion avec Content-Type existant (POST /devices + token)', async () => { - setAuthToken('tok-123'); - stubFetch(() => ok({ deviceId: 'a' })); - await api.registerDevice('a'); - const headers = new Headers(calls[0].init.headers); - assert.equal(headers.get('Authorization'), 'Bearer tok-123'); - assert.equal(headers.get('Content-Type'), 'application/json'); -}); \ No newline at end of file diff --git a/mobile/tests/dbClient.test.ts b/mobile/tests/dbClient.test.ts deleted file mode 100644 index c45a284..0000000 --- a/mobile/tests/dbClient.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { - isBrokenConnectionError, - withDatabaseRetry, - type DatabaseRetryDeps, -} from '../services/db/client'; -import type { SQLiteDatabase } from 'expo-sqlite'; - -function brokenConnectionError(): Error { - return new Error( - "Call to function 'NativeDatabase.prepareAsync' has been rejected.\n→ Caused by: java.lang.NullPointerException: java.lang.NullPointerException", - ); -} - -test('isBrokenConnectionError recognizes the expo-sqlite Android NPE signature', () => { - assert.equal(isBrokenConnectionError(brokenConnectionError()), true); - assert.equal( - isBrokenConnectionError( - new Error( - "Call to function 'NativeDatabase.execAsync' has been rejected.\n→ Caused by: java.lang.NullPointerException: java.lang.NullPointerException", - ), - ), - true, - ); -}); - -test('isBrokenConnectionError ignores regular and non-Error values', () => { - assert.equal(isBrokenConnectionError(new Error('disk I/O error')), false); - assert.equal(isBrokenConnectionError(null), false); - assert.equal(isBrokenConnectionError('NativeDatabase.prepareAsync'), false); -}); - -test('withDatabaseRetry reconnects once on a dead connection and retries', async () => { - let current: SQLiteDatabase = { - runAsync: async () => { - throw brokenConnectionError(); - }, - } as unknown as SQLiteDatabase; - let getCalls = 0; - let recoverCalls = 0; - - const deps: DatabaseRetryDeps = { - get: async () => { - getCalls++; - return current; - }, - recover: async () => { - recoverCalls++; - current = { - runAsync: async () => 42 as never, - } as unknown as SQLiteDatabase; - return current; - }, - }; - - const result = await withDatabaseRetry(async (db) => db.runAsync('SELECT 1'), deps); - assert.equal(result, 42); - assert.equal(recoverCalls, 1); - assert.equal(getCalls, 1); -}); - -test('withDatabaseRetry recovers when the open itself rejects with the NPE signature', async () => { - let recoverCalls = 0; - const deps: DatabaseRetryDeps = { - get: async () => { - throw brokenConnectionError(); - }, - recover: async () => { - recoverCalls++; - return { - runAsync: async () => 42 as never, - } as unknown as SQLiteDatabase; - }, - }; - - const result = await withDatabaseRetry(async (db) => db.runAsync('SELECT 1'), deps); - assert.equal(result, 42); - assert.equal(recoverCalls, 1); -}); - -test('withDatabaseRetry does not recover on a regular database error', async () => { - let recoverCalls = 0; - const deps: DatabaseRetryDeps = { - get: async () => - ({ - runAsync: async () => { - throw new Error('disk I/O error'); - }, - }) as unknown as SQLiteDatabase, - recover: async () => { - recoverCalls++; - return {} as SQLiteDatabase; - }, - }; - - await assert.rejects(() => withDatabaseRetry(async (db) => db.runAsync('SELECT 1'), deps), { - message: 'disk I/O error', - }); - assert.equal(recoverCalls, 0); -}); - -test('withDatabaseRetry converges on a broken retry (recovery does not loop forever)', async () => { - const deps: DatabaseRetryDeps = { - get: async () => ({} as SQLiteDatabase), - recover: async () => ({} as SQLiteDatabase), - }; - - await assert.rejects( - () => - withDatabaseRetry(async () => { - throw brokenConnectionError(); - }, deps), - (error: unknown) => - error instanceof Error && isBrokenConnectionError(error), - ); -}); \ No newline at end of file diff --git a/mobile/tests/e2e.live.test.ts b/mobile/tests/e2e.live.test.ts deleted file mode 100644 index 34ced6b..0000000 --- a/mobile/tests/e2e.live.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Smoke test live contre un backend démarré (POSTGRES + serveur Go) : -// docker compose up postgres -d (racine repo) -// cd backend && go run cmd/server/main.go -// cd mobile && npm run test:e2e -// -// NON agrégé dans `npm test` (il exige un backend fonctionnel). Si le serveur -// est injoignable, les tests sont marqués skipped, pas échoués. - -import { test, beforeEach, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import Database from 'better-sqlite3'; -import { MIGRATIONS, type MigrationDb } from '../services/db/migrations'; -import type { DbSession } from '../services/db/session'; -import { __setDbForTests } from '../services/db/session'; -import { api, setAuthToken } from '../api/client'; -import { - clearActiveUserId, - enqueuePendingOperation, - getPendingOperations, - setActiveUserId, -} from '../services/db'; -import { pushPendingOps, refreshPermissions, resetPermissionCachedAtForTests } from '../features/syncOutbox'; - -const BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL ?? 'http://localhost:8080/api/v1'; -const ADMIN_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin'; -const ADMIN_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin'; - -type Harness = MigrationDb & DbSession; - -let h: Harness; - -function createHarness(): Harness { - const sqlite = new Database(':memory:'); - const harness: Harness = { - execAsync: async (sql: string) => { - sqlite.exec(sql); - }, - runAsync: async (sql: string, ...params: unknown[]) => { - sqlite.prepare(sql).run(...params); - }, - getFirstAsync: async (sql: string, ...params: unknown[]) => - (sqlite.prepare(sql).get(...params) ?? null) as never, - getAllAsync: async (sql: string, ...params: unknown[]) => - sqlite.prepare(sql).all(...params) as never, - withExclusiveTransactionAsync: async (task: (txn: Harness) => Promise) => { - sqlite.exec('BEGIN'); - try { - await task(harness); - sqlite.exec('COMMIT'); - } catch (error) { - sqlite.exec('ROLLBACK'); - throw error; - } - }, - }; - return harness; -} - -let deviceId: string; - -function newDeviceId(): string { - const bytes = new Uint8Array(16); - for (let i = 0; i < 16; i++) { - bytes[i] = Math.floor(Math.random() * 256); - } - return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); -} - -function isBackendUp(): boolean { - return !!process.env.CI_E2E_REQUIRE_BACKEND; -} - -beforeEach(async () => { - h = createHarness(); - for (const migration of MIGRATIONS) { - await migration.up(h); - } - __setDbForTests(h); - resetPermissionCachedAtForTests(); -}); - -afterEach(() => { - clearActiveUserId(); - __setDbForTests(null); - setAuthToken(null); -}); - -test('bout en bout : register + login admin → push outbox → snapshot → relecture serveur', async (t) => { - try { - await api.health(); - } catch (error) { - if (!isBackendUp()) { - t.skip(`backend injoignable sur ${BASE_URL} — lancer serveur + postgres`); - return; - } - throw error; - } - - // Enregistrement du device (idempotent) puis login = SEULE porte de token. - deviceId = newDeviceId(); - await api.registerDevice(deviceId); - const login = await api.login(ADMIN_USERNAME, ADMIN_PASSWORD, deviceId); - setAuthToken(login.data.token); - await setActiveUserId(login.data.user.id); - assert.equal(login.data.user.is_admin, true, 'l’admin E2E doit exister (ADMIN_*)'); - - // 1. Enqueue un CREATE_RESOURCE puis push (scopé au compte connecté) - const name = `e2e-${deviceId.slice(0, 8)}`; - const resourceId = newDeviceId(); - const opId = await enqueuePendingOperation({ - resourceId, - resourceType: 'folder', - refType: 'resource', - refId: null, - operation: 'create_resource', - payload: { name }, - }); - const push = await pushPendingOps(); - assert.deepEqual(push, { pushed: 1, retried: 0 }); - - const local = await getPendingOperations(); - const done = local.find((op) => op.id === opId); - assert.equal(done?.status, 'completed'); - assert.equal(done?.attempts, 0); - - // 2. Relecture côté serveur : le dossier racine créé est visible - const folders = await api.listFolders(); - assert.ok(folders.data.some((f) => f.name === name), 'dossier absent côté serveur'); - - // 3. Snapshot des permissions → persisté localement - const snapshotCount = await refreshPermissions(); - assert.ok(snapshotCount < 10_000, `snapshot trop volumineux (${snapshotCount})`); -}); \ No newline at end of file diff --git a/mobile/tests/migrations.test.ts b/mobile/tests/migrations.test.ts deleted file mode 100644 index f0b4ae4..0000000 --- a/mobile/tests/migrations.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import Database from 'better-sqlite3'; -import { MIGRATIONS, migrateDatabase } from '../services/db/migrations'; -import { DATABASE_VERSION, DEVICE_USER_ID_KEY } from '../services/db/schema'; - -type Harness = { - execAsync(sql: string): Promise; - runAsync(sql: string, ...params: unknown[]): Promise; - getFirstAsync(sql: string, ...params: unknown[]): Promise; - getAllAsync(sql: string, ...params: unknown[]): Promise; - withExclusiveTransactionAsync(task: (txn: Harness) => Promise): Promise; - raw: Database.Database; -}; - -function createHarness(): Harness { - const db = new Database(':memory:'); - const self: Harness = { - execAsync: async (sql) => { - db.exec(sql); - }, - runAsync: async (sql, ...params) => { - db.prepare(sql).run(...params); - }, - getFirstAsync: async (sql, ...params) => - (db.prepare(sql).get(...params) ?? null) as never, - getAllAsync: async (sql, ...params) => db.prepare(sql).all(...params) as never, - withExclusiveTransactionAsync: async (task) => { - db.exec('BEGIN'); - try { - const result = await task(self); - db.exec('COMMIT'); - return result; - } catch (err) { - db.exec('ROLLBACK'); - throw err; - } - }, - raw: db, - }; - return self; -} - -async function userVersion(h: Harness): Promise { - const row = await h.getFirstAsync<{ user_version: number }>('PRAGMA user_version'); - return row?.user_version ?? 0; -} - -async function columnNames(h: Harness, table: string): Promise { - const rows = await h.getAllAsync<{ name: string }>(`PRAGMA table_info(${table})`); - return rows.map((r) => r.name); -} - -async function foreignKeyViolations(h: Harness): Promise { - return h.getAllAsync('PRAGMA foreign_key_check'); -} - -const ROOT_URI = 'content://com.android.providers.documents/tree/primary%3ADocuments'; -const SUB_URI = 'content://com.android.providers.documents/tree/primary%3ADocuments%2Fphotos%20encod%2520ed'; -const EMPTY_URI = 'content://com.android.providers.documents/document/empty'; -const MUSIC_URI = 'content://com.android.providers.documents/tree/primary%3AMusic'; -const NOTE_URI = 'content://com.android.providers.documents/document/primary%20note.txt'; -const PIC_URI = 'content://com.android.providers.documents/document/primary%3Aphotos%2Fpic%20.jpg'; -const SONG_URI = 'content://com.android.providers.documents/document/primary%3ASong%20encod%2520.mp3'; - -async function seedLegacyTree(h: Harness): Promise { - await h.execAsync(`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri) - VALUES ('${ROOT_URI}', 'Docs', 1, 'local', 1000, NULL);`); - await h.execAsync(`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri) - VALUES ('${SUB_URI}', 'Photos 2024', 1, 'local', 2000, '${ROOT_URI}');`); - await h.execAsync(`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri) - VALUES ('${EMPTY_URI}', 'Empty', 1, 'cloud', 3000, NULL);`); - await h.execAsync(`INSERT INTO folders (uri, name, "exists", sync_status, added_at, parent_uri) - VALUES ('${MUSIC_URI}', 'Music', 0, 'local-cloud', 4000, NULL);`); - await h.execAsync(`INSERT INTO files (uri, name, folder_uri, extension, size, "type", "exists", last_modified, sync_status, added_at) - VALUES ('${NOTE_URI}', 'note.txt', '${ROOT_URI}', 'txt', 10, 'text/plain', 1, 1000, 'local', 1000);`); - await h.execAsync(`INSERT INTO files (uri, name, folder_uri, extension, size, "type", "exists", last_modified, sync_status, added_at) - VALUES ('${PIC_URI}', 'pic.jpg', '${SUB_URI}', 'jpg', 20, 'image/jpeg', 1, 2000, 'local-cloud', 2000);`); - await h.execAsync(`INSERT INTO files (uri, name, folder_uri, extension, size, "type", "exists", last_modified, sync_status, added_at) - VALUES ('${SONG_URI}', 'song.mp3', '${MUSIC_URI}', 'mp3', 30, 'audio/mpeg', 0, 3000, 'cloud', 3000);`); -} - -test('fresh migrate v0 → v5 creates full schema and seeds device id', async () => { - const h = createHarness(); - await migrateDatabase(h); - - assert.equal(await userVersion(h), DATABASE_VERSION, `user_version should be ${DATABASE_VERSION}`); - - const folderCols = await columnNames(h, 'folders'); - for (const col of ['resource_id', 'uri', 'parent_resource_id', 'owner_id', 'updated_at']) { - assert.ok(folderCols.includes(col), `folders should have column ${col}`); - } - assert.ok(!folderCols.includes('parent_uri'), 'parent_uri must be replaced by parent_resource_id'); - - const fileCols = await columnNames(h, 'files'); - for (const col of ['resource_id', 'folder_resource_id', 'owner_id', 'updated_at']) { - assert.ok(fileCols.includes(col), `files should have column ${col}`); - } - - for (const table of [ - 'resource_permissions', - 'shares', - 'recipients', - 'share_links', - 'pending_operations', - ]) { - const names = await columnNames(h, table); - assert.ok(names.length > 0, `table ${table} must exist`); - } - - const permCols = await columnNames(h, 'resource_permissions'); - assert.ok(permCols.includes('user_id'), 'resource_permissions must have user_id (v5)'); - const opCols = await columnNames(h, 'pending_operations'); - assert.ok(opCols.includes('user_id'), 'pending_operations must have user_id (v5)'); - - const device = await h.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - DEVICE_USER_ID_KEY, - ); - assert.ok(device && /^[0-9a-f]{32}$/.test(device.value), 'device_user_id seeded as 32-hex'); - - assert.deepEqual(await foreignKeyViolations(h), [], 'no orphaned FKs after fresh migrate'); -}); - -test('v5 : UNIQUE de resource_permissions scopé par (user_id, resource_id, resource_type)', async () => { - const h = createHarness(); - await migrateDatabase(h); - - const row = await h.getFirstAsync<{ sql: string }>( - `SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 'resource_permissions'`, - ); - assert.ok(row, 'table sql present'); - assert.match(row.sql, /UNIQUE\s*\(user_id,\s*resource_id,\s*resource_type\)/); - - // Deux comptes peuvent partager la même ressource sans collision. - await h.runAsync( - `INSERT INTO resource_permissions (user_id, resource_id, resource_type, effective_access, inherit, owner_id, cached_at, updated_at) - VALUES ('u1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'folder', 'owner', 1, NULL, 1, 1)`, - ); - await h.runAsync( - `INSERT INTO resource_permissions (user_id, resource_id, resource_type, effective_access, inherit, owner_id, cached_at, updated_at) - VALUES ('u2', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'folder', 'viewer', 0, NULL, 1, 1)`, - ); - await assert.rejects( - () => - h.runAsync( - `INSERT INTO resource_permissions (user_id, resource_id, resource_type, effective_access, inherit, owner_id, cached_at, updated_at) - VALUES ('u1', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'folder', 'editor', 1, NULL, 1, 1)`, - ), - /UNIQUE constraint failed/, - ); -}); - -test('user_version is transactional (rollback restores previous version)', async () => { - const h = createHarness(); - await migrateDatabase(h); - const before = await userVersion(h); - - h.raw.exec('BEGIN'); - h.raw.exec('PRAGMA user_version = 999'); - h.raw.exec('ROLLBACK'); - - assert.equal(await userVersion(h), before, 'user_version must be rolled back'); -}); - -test('realistic v2 → v4 tree is preserved with correct reparenting', async () => { - const h = createHarness(); - await migrateDatabase(h, 2); - await seedLegacyTree(h); - - await migrateDatabase(h); - - assert.equal(await userVersion(h), DATABASE_VERSION); - - const folders = await h.getAllAsync<{ - resource_id: string; - uri: string | null; - name: string; - parent_resource_id: string | null; - owner_id: string; - added_at: number; - updated_at: number; - }>('SELECT resource_id, uri, name, parent_resource_id, owner_id, added_at, updated_at FROM folders'); - const files = await h.getAllAsync<{ - resource_id: string; - uri: string | null; - name: string; - folder_resource_id: string; - owner_id: string; - added_at: number; - updated_at: number; - }>('SELECT resource_id, uri, name, folder_resource_id, owner_id, added_at, updated_at FROM files'); - - assert.equal(folders.length, 4, 'folders count preserved'); - assert.equal(files.length, 3, 'files count preserved'); - - for (const f of folders) { - assert.match(f.resource_id, /^[0-9a-f]{32}$/, 'resource_id must be 32-hex once'); - assert.ok(f.owner_id, 'owner_id must be set (never NULL implicit)'); - assert.equal(f.updated_at, f.added_at, 'backfilled updated_at = added_at'); - } - for (const f of files) { - assert.match(f.resource_id, /^[0-9a-f]{32}$/, 'resource_id must be 32-hex'); - assert.ok(f.owner_id, 'owner_id must be set'); - } - const resourceIds = [...folders.map((f) => f.resource_id), ...files.map((f) => f.resource_id)]; - assert.equal(new Set(resourceIds).size, resourceIds.length, 'all resource ids unique in the same table'); - - const device = await h.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - DEVICE_USER_ID_KEY, - ); - for (const f of [...folders, ...files]) { - assert.equal(f.owner_id, device!.value, `owner_id must be device_user_id, got ${f.owner_id}`); - } - - const byUri = new Map(folders.map((f) => [f.uri, f])); - const root = byUri.get(ROOT_URI); - const sub = byUri.get(SUB_URI); - assert.ok(root && sub, 'local uris preserved on local rows'); - assert.equal(sub.parent_resource_id, root.resource_id, 'child folder reparented to root resource_id'); - - const byName = new Map(folders.map((f) => [f.name, f])); - const empty = byName.get('Empty'); - assert.equal(empty!.parent_resource_id, null, 'root folder stays root'); - - const note = files.find((f) => f.name === 'note.txt'); - const pic = files.find((f) => f.name === 'pic.jpg'); - const song = files.find((f) => f.name === 'song.mp3'); - assert.equal(note!.folder_resource_id, root.resource_id, 'file linked to root folder'); - assert.equal(pic!.folder_resource_id, sub.resource_id, 'file linked to nested folder'); - assert.equal(song!.folder_resource_id, byName.get('Music')!.resource_id, 'cloud file linked to cloud folder'); - - assert.deepEqual(await foreignKeyViolations(h), [], 'no orphaned FKs after v2→v4'); -}); - -test('cascade delete works through the rebuilt self-FK', async () => { - const h = createHarness(); - await migrateDatabase(h, 2); - await seedLegacyTree(h); - await migrateDatabase(h); - - const folders = await h.getAllAsync<{ resource_id: string; name: string; parent_resource_id: string | null }>( - 'SELECT resource_id, name, parent_resource_id FROM folders', - ); - const root = folders.find((f) => f.name === 'Docs')!; - - await h.runAsync('DELETE FROM folders WHERE resource_id = ?', root.resource_id); - - const names = await h.getAllAsync<{ name: string }>('SELECT name FROM folders'); - assert.equal(names.length, 2, 'root and its subtree removed (Docs, Photos 2024)'); - const files = await h.getAllAsync<{ name: string }>('SELECT name FROM files'); - assert.equal(files.length, 1, 'only Music file remains (note + pic cascaded)'); - assert.deepEqual(await foreignKeyViolations(h), [], 'no orphans after cascade'); -}); - -test('v4 crash is atomic: rollback keeps v3 schema and data, rerun succeeds', async () => { - const h = createHarness(); - await migrateDatabase(h, 3); - await seedLegacyTree(h); - - const originalExec = h.execAsync.bind(h); - h.execAsync = async (sql) => { - if (sql.startsWith('DROP TABLE files;')) { - throw new Error('simulated crash during rebuild'); - } - return originalExec(sql); - }; - - const v4 = MIGRATIONS.find((m) => m.version === 4); - assert.ok(v4, 'v4 migration exists'); - await assert.rejects(() => v4.up(h), /simulated crash/); - - assert.equal(await userVersion(h), 3, 'user_version unchanged after failed rebuild'); - assert.ok((await columnNames(h, 'folders')).includes('parent_uri'), 'old folders schema restored'); - const folders = await h.getAllAsync<{ name: string }>('SELECT name FROM folders'); - assert.equal(folders.length, 4, 'data intact after rollback'); - - h.execAsync = originalExec; - await migrateDatabase(h); - - assert.equal(await userVersion(h), DATABASE_VERSION, 'rerun completes to newest version'); - assert.ok((await columnNames(h, 'folders')).includes('resource_id')); - const files = await h.getAllAsync<{ name: string }>('SELECT name FROM files'); - assert.equal(files.length, 3, 'files preserved after rerun'); - assert.deepEqual(await foreignKeyViolations(h), [], 'no orphans after rerun'); -}); - -test('concurrent migrateDatabase calls are single-flight', async () => { - const h = createHarness(); - await Promise.all([migrateDatabase(h), migrateDatabase(h)]); - assert.equal(await userVersion(h), DATABASE_VERSION); -}); \ No newline at end of file diff --git a/mobile/tests/repositories.test.ts b/mobile/tests/repositories.test.ts deleted file mode 100644 index 5dab8f9..0000000 --- a/mobile/tests/repositories.test.ts +++ /dev/null @@ -1,442 +0,0 @@ -import { test, beforeEach, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import Database from 'better-sqlite3'; -import { MIGRATIONS, type MigrationDb } from '../services/db/migrations'; -import type { DbSession } from '../services/db/session'; -import { __setDbForTests } from '../services/db/session'; -import { - saveFolder, - getFolders, - getFolder, - removeFolder, - saveFile, - getFiles, - canAccess, - saveResourcePermission, - saveShare, - getShares, - createShareLink, - getShareLinks, - incrementLinkDownloads, - enqueuePendingOperation, - getPendingOperations, - getNextQueuedOperation, - markPendingOperation, - getDeviceUserId, - DEVICE_USER_ID_KEY, - PERMISSION_TTL_MS, -} from '../services/db'; -import type { FileEntry } from '../services/safDirectory.types'; -import { MAX_PENDING_ATTEMPTS } from '../services/db/repositories/pendingOps'; - -type Harness = MigrationDb & DbSession; - -let h: Harness; - -function createHarness(): Harness { - const sqlite = new Database(':memory:'); - const harness: Harness = { - execAsync: async (sql: string) => { - sqlite.exec(sql); - }, - runAsync: async (sql: string, ...params: unknown[]) => { - sqlite.prepare(sql).run(...params); - }, - getFirstAsync: async (sql: string, ...params: unknown[]) => - (sqlite.prepare(sql).get(...params) ?? null) as never, - getAllAsync: async (sql: string, ...params: unknown[]) => - sqlite.prepare(sql).all(...params) as never, - withExclusiveTransactionAsync: async (task: (txn: Harness) => Promise) => { - sqlite.exec('BEGIN'); - try { - await task(harness); - sqlite.exec('COMMIT'); - } catch (error) { - sqlite.exec('ROLLBACK'); - throw error; - } - }, - }; - return harness; -} - -beforeEach(async () => { - h = createHarness(); - for (const migration of MIGRATIONS) { - await migration.up(h); - } - __setDbForTests(h); -}); - -afterEach(() => { - __setDbForTests(null); -}); - -function fileEntry(uri: string, overrides: Partial = {}): FileEntry { - return { - uri, - name: uri.split('/').pop() ?? uri, - isDirectory: false, - extension: uri.split('.').pop() ?? '', - exists: true, - size: 10, - type: 'application/octet-stream', - lastModified: 1, - ...overrides, - }; -} - -async function setOwner( - table: 'folders' | 'files', - resourceId: string, - ownerId: string, -): Promise { - await h.runAsync(`UPDATE ${table} SET owner_id = ? WHERE resource_id = ?`, ownerId, resourceId); -} - -test('folder upsert is idempotent by uri (same resource_id, single row)', async () => { - const a = await saveFolder({ uri: 'content://a', name: 'A' }); - const b = await saveFolder({ uri: 'content://a', name: 'A renamed' }); - - assert.equal(a.resource_id, b.resource_id); - assert.equal(b.name, 'A renamed'); - assert.equal(b.uri, 'content://a'); - assert.equal((await getFolders()).length, 1); -}); - -test('distinct uris produce distinct rows', async () => { - await saveFolder({ uri: 'content://a', name: 'A' }); - await saveFolder({ uri: 'content://b', name: 'B' }); - assert.equal((await getFolders()).length, 2); -}); - -test('cloud-only folder gains a uri without duplicating (reconciled by resource_id)', async () => { - const cloud = await saveFolder({ uri: null, name: 'Cloud', resource_id: 'abc123' }); - assert.equal(cloud.uri, null); - const wired = await saveFolder({ uri: 'content://cloud', name: 'Cloud', resource_id: 'abc123' }); - assert.equal(wired.resource_id, 'abc123'); - assert.equal(wired.uri, 'content://cloud'); - assert.equal((await getFolders()).length, 1); -}); - -test('inserting a folder with a nonexistent parent throws a FK error (no silent orphan)', async () => { - await assert.rejects( - () => saveFolder({ uri: 'content://child', name: 'C' }, { parentResourceId: 'does-not-exist' }), - /FOREIGN KEY/, - ); -}); - -test('inserting a file before its parent folder throws a FK error', async () => { - await assert.rejects( - () => saveFile(fileEntry('content://orphan/f.pdf'), 'missing-folder'), - /FOREIGN KEY/, - ); -}); - -test('re-saving a folder without a parent keeps the existing parent', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const child = await saveFolder({ uri: 'content://r/c', name: 'C' }, { parentResourceId: root.resource_id }); - const reSaved = await saveFolder({ uri: 'content://r/c', name: 'C2' }); - assert.equal(reSaved.parent_resource_id, root.resource_id); -}); - -test('a full SAF walk repeated twice is a no-op (BFS order, uri reconciliation)', async () => { - const root = await saveFolder({ uri: 'content://root', name: 'Root' }); - const sub = await saveFolder({ uri: 'content://root/sub', name: 'Sub' }, { parentResourceId: root.resource_id }); - await saveFile(fileEntry('content://root/sub/f1.pdf'), sub.resource_id); - - const root2 = await saveFolder({ uri: 'content://root', name: 'Root' }); - const sub2 = await saveFolder({ uri: 'content://root/sub', name: 'Sub' }, { parentResourceId: root2.resource_id }); - await saveFile(fileEntry('content://root/sub/f1.pdf'), sub2.resource_id); - - assert.equal(root.resource_id, root2.resource_id); - assert.equal(sub.resource_id, sub2.resource_id); - assert.equal(sub2.parent_resource_id, root2.resource_id); - assert.equal((await getFolders()).length, 2); - assert.equal((await getFiles()).length, 1); - const stored = await getFiles(sub2.resource_id); - assert.equal(stored.length, 1); -}); - -test('removing a root folder cascades to its subtree and files', async () => { - const root = await saveFolder({ uri: 'content://root', name: 'Root' }); - const sub = await saveFolder({ uri: 'content://root/sub', name: 'Sub' }, { parentResourceId: root.resource_id }); - await saveFile(fileEntry('content://root/sub/f1.pdf'), sub.resource_id); - - await removeFolder(root.resource_id); - - assert.equal((await getFolders()).length, 0); - assert.equal((await getFiles()).length, 0); -}); - -test('canAccess: nothing granted => denied, owner fallback grants owner', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const sub = await saveFolder({ uri: 'content://r/s', name: 'S' }, { parentResourceId: root.resource_id }); - const f = await saveFile(fileEntry('content://r/s/f.pdf'), sub.resource_id); - - const device = await getDeviceUserId(); - await setOwner('folders', root.resource_id, 'other'); - await setOwner('folders', sub.resource_id, 'other'); - await setOwner('files', f.resource_id, 'other'); - - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).allowed, false); - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).allowed, false); - - await setOwner('folders', sub.resource_id, device); - assert.equal((await canAccess(f.resource_id, 'file', 'owner')).allowed, true); - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).source, 'owner'); -}); - -test('canAccess: granted permission on an ancestor inherits through deep folders', async () => { - const ids: string[] = []; - let parentResourceId: string | null = null; - for (let depth = 0; depth < 5; depth++) { - const folder = await saveFolder( - { uri: `content://chain/${depth}`, name: `n${depth}` }, - { parentResourceId }, - ); - ids.push(folder.resource_id); - parentResourceId = folder.resource_id; - } - const f = await saveFile(fileEntry('content://chain/f.pdf'), parentResourceId!); - const device = await getDeviceUserId(); - for (const id of ids) await setOwner('folders', id, 'other'); - await setOwner('files', f.resource_id, 'other'); - - await saveResourcePermission({ - resource_id: ids[0], - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: true, - }); - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).allowed, true, 'viewer inherited from root'); - assert.equal((await canAccess(f.resource_id, 'file', 'editor')).allowed, false, 'viewer is not editor'); - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).source, 'inherited'); -}); - -test('canAccess: inherit=false blocks propagation but allows the node itself', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const sub = await saveFolder({ uri: 'content://r/s', name: 'S' }, { parentResourceId: root.resource_id }); - const f = await saveFile(fileEntry('content://r/s/f.pdf'), sub.resource_id); - const device = await getDeviceUserId(); - await setOwner('folders', root.resource_id, 'other'); - await setOwner('folders', sub.resource_id, 'other'); - await setOwner('files', f.resource_id, 'other'); - - await saveResourcePermission({ - resource_id: root.resource_id, - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: false, - }); - assert.equal((await canAccess(root.resource_id, 'folder', 'viewer')).allowed, true, 'root itself keeps viewer'); - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).allowed, false, 'inheritance stopped'); - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).allowed, false); - - await saveResourcePermission({ - resource_id: sub.resource_id, - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: true, - }); - assert.equal((await canAccess(f.resource_id, 'file', 'viewer')).allowed, true, 'closer ancestor grants descend'); -}); - -test('canAccess: an expired permission is denied and does not propagate', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const sub = await saveFolder({ uri: 'content://r/s', name: 'S' }, { parentResourceId: root.resource_id }); - const device = await getDeviceUserId(); - await setOwner('folders', root.resource_id, 'other'); - await setOwner('folders', sub.resource_id, 'other'); - - await saveResourcePermission({ - resource_id: root.resource_id, - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: true, - expiresAt: Date.now() - 1000, - }); - assert.equal((await canAccess(root.resource_id, 'folder', 'viewer')).allowed, false); - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).allowed, false); -}); - -test('canAccess: stale cache only allows read (viewer), fresh cache allows the granted level', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const sub = await saveFolder({ uri: 'content://r/s', name: 'S' }, { parentResourceId: root.resource_id }); - const device = await getDeviceUserId(); - await setOwner('folders', root.resource_id, 'other'); - await setOwner('folders', sub.resource_id, 'other'); - - await saveResourcePermission({ - resource_id: root.resource_id, - resourceType: 'folder', - effectiveAccess: 'editor', - inherit: true, - }); - assert.equal((await canAccess(sub.resource_id, 'folder', 'editor')).allowed, true, 'fresh editor grant'); - - await h.runAsync( - 'UPDATE resource_permissions SET cached_at = ? WHERE resource_id = ?', - Date.now() - PERMISSION_TTL_MS - 60_000, - root.resource_id, - ); - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).allowed, true, 'stale still allows read'); - assert.equal((await canAccess(sub.resource_id, 'folder', 'editor')).allowed, false, 'stale blocks writes'); - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).stale, true); -}); - -test('canAccess: a cached decision on the exact node is authoritative even if weaker', async () => { - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const sub = await saveFolder({ uri: 'content://r/s', name: 'S' }, { parentResourceId: root.resource_id }); - const device = await getDeviceUserId(); - await setOwner('folders', root.resource_id, 'other'); - await setOwner('folders', sub.resource_id, 'other'); - - await saveResourcePermission({ - resource_id: root.resource_id, - resourceType: 'folder', - effectiveAccess: 'editor', - inherit: true, - }); - await saveResourcePermission({ - resource_id: sub.resource_id, - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: true, - }); - - assert.equal((await canAccess(sub.resource_id, 'folder', 'viewer')).allowed, true); - assert.equal((await canAccess(sub.resource_id, 'folder', 'editor')).allowed, false, 'exact viewer cache overrides inherited editor'); -}); - -test('outbox: ops on the same resource are drained in FIFO order (id tie-break)', async () => { - const id1 = await enqueuePendingOperation({ operation: 'create_resource', resourceId: 'a' }); - const id2 = await enqueuePendingOperation({ operation: 'update_metadata', resourceId: 'a' }); - const id3 = await enqueuePendingOperation({ operation: 'delete_resource', resourceId: 'a' }); - await h.runAsync('UPDATE pending_operations SET created_at = ?', 1000); - - const ops = await getPendingOperations(); - assert.deepEqual( - ops.map((o) => o.operation), - ['create_resource', 'update_metadata', 'delete_resource'], - ); - - const first = await getNextQueuedOperation(); - assert.equal(first!.id, id1); - await markPendingOperation(first!.id, 'completed'); - const second = await getNextQueuedOperation(); - assert.equal(second!.id, id2); - await markPendingOperation(second!.id, 'completed'); - assert.equal((await getNextQueuedOperation())!.id, id3); -}); - -test('outbox: a failure stays pending with a backoff, then dead-letters after max attempts', async () => { - const id = await enqueuePendingOperation({ operation: 'create_resource', resourceId: 'a' }); - - await markPendingOperation(id, 'failed', 'boom'); - let row = await h.getFirstAsync<{ - status: string; - attempts: number; - next_retry_at: number | null; - }>('SELECT status, attempts, next_retry_at FROM pending_operations WHERE id = ?', id); - assert.equal(row!.status, 'pending'); - assert.equal(row!.attempts, 1); - assert.ok((row!.next_retry_at ?? 0) > Date.now(), 'retry scheduled in the future'); - - assert.equal(await getNextQueuedOperation(), null, 'backoff respects next_retry_at'); - - await h.runAsync('UPDATE pending_operations SET next_retry_at = ? WHERE id = ?', 0, id); - const due = await getNextQueuedOperation(); - assert.ok(due); - assert.equal(due.attempts, 1); - - for (let i = 0; i < MAX_PENDING_ATTEMPTS - 1; i++) { - await markPendingOperation(id, 'failed', 'still failing'); - } - row = await h.getFirstAsync<{ - status: string; - attempts: number; - next_retry_at: number | null; - }>('SELECT status, attempts, next_retry_at FROM pending_operations WHERE id = ?', id); - assert.equal(row!.status, 'failed', 'dead-lettered'); - assert.equal(row!.attempts, MAX_PENDING_ATTEMPTS); - assert.equal(row!.next_retry_at, null); - assert.equal(await getNextQueuedOperation(), null, 'dead-lettered ops are never re-picked'); - - const share = await saveShare({ - resourceId: 'a', - resourceType: 'folder', - recipientType: 'user', - recipientId: 'u1', - relation: 'viewer', - }); - assert.equal(share.pushStatus, 'synced'); -}); - -test('shares and share links derive pushStatus from the outbox', async () => { - const share = await saveShare({ - resourceId: 'res-1', - resourceType: 'folder', - recipientType: 'user', - recipientId: 'u1', - relation: 'editor', - }); - assert.equal(share.pushStatus, 'synced'); - - const opId = await enqueuePendingOperation({ - refType: 'share', - refId: share.id, - operation: 'share', - resourceId: 'res-1', - resourceType: 'folder', - }); - assert.equal((await getShares('res-1', 'folder'))[0].pushStatus, 'pending'); - - await markPendingOperation(opId, 'failed', 'nope'); - assert.equal( - (await getShares('res-1', 'folder'))[0].pushStatus, - 'pending', - 'a single failure schedules a retry, share stays pending', - ); - - for (let attempt = 1; attempt < MAX_PENDING_ATTEMPTS; attempt++) { - await markPendingOperation(opId, 'failed', 'still failing'); - } - assert.equal((await getShares('res-1', 'folder'))[0].pushStatus, 'failed', 'dead-lettered'); - - await markPendingOperation(opId, 'completed'); - const shares = await getShares('res-1', 'folder'); - assert.equal(shares.length, 1); - assert.equal(shares[0].pushStatus, 'synced', 'no pending/failed left'); - - const link = await createShareLink({ resourceId: 'res-1', resourceType: 'folder' }); - assert.match(link.token, /^[0-9a-f]{32}$/); - assert.equal(link.pushStatus, 'synced'); - - const linkOpId = await enqueuePendingOperation({ - refType: 'share_link', - refId: link.id, - operation: 'create_link', - }); - assert.equal((await getShareLinks('res-1', 'folder'))[0].pushStatus, 'pending'); - await markPendingOperation(linkOpId, 'completed'); - assert.equal((await getShareLinks('res-1', 'folder'))[0].pushStatus, 'synced'); - - await incrementLinkDownloads(link.id); - assert.equal((await getShareLinks('res-1', 'folder'))[0].downloadsCount, 1); -}); - -test('device user id is a stable lowercase 32-hex string shared by all rows', async () => { - const device = await getDeviceUserId(); - assert.match(device, /^[0-9a-f]{32}$/); - - const root = await saveFolder({ uri: 'content://r', name: 'R' }); - const rootRow = await getFolder(root.resource_id); - assert.equal(rootRow!.owner_id, device); - - const seeded = await h.getFirstAsync<{ value: string }>( - 'SELECT "value" FROM user_preferences WHERE "key" = ?', - DEVICE_USER_ID_KEY, - ); - assert.equal(seeded!.value, device); -}); \ No newline at end of file diff --git a/mobile/tests/safWalk.test.ts b/mobile/tests/safWalk.test.ts deleted file mode 100644 index d39ebf6..0000000 --- a/mobile/tests/safWalk.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { test, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import { __setSafWalkForTests, listFoldersChunked } from '../services/safWalk'; -import type { DirectoryEntry, Folder } from '../services/safDirectory.types'; - -function dir(uri: string): Folder { - return { uri, name: uri.split('/').pop() ?? uri, isDirectory: true, exists: true }; -} - -function file(uri: string): DirectoryEntry { - return { - uri, - name: uri.split('/').pop() ?? uri, - isDirectory: false, - extension: '', - exists: true, - size: 1, - type: 'text/plain', - lastModified: 0, - }; -} - -function info(uri: string): { uri: string; name: string; exists: boolean } { - return { uri, name: uri.split('/').pop() ?? uri, exists: true }; -} - -const tree: Record = { - '/root': [dir('/root/a'), dir('/root/b'), dir('/root/c'), file('/root/f1')], - '/root/a': [dir('/root/a/x'), file('/root/a/f2')], - '/root/a/x': [], - '/root/b': [file('/root/b/f3')], - '/root/c': [], -}; - -const restoreFns: Array<() => void> = []; - -afterEach(() => { - while (restoreFns.length) restoreFns.pop()?.(); -}); - -function fakeSaf() { - return __setSafWalkForTests({ - list: (uri) => tree[uri] ?? [], - info, - }); -} - -test('listFoldersChunked : parcours récursif identique à listFolders', async () => { - restoreFns.push(fakeSaf()); - const folders = await listFoldersChunked('/root', { recursive: true, includeRoot: true }); - assert.deepEqual( - folders.map((f) => f.uri), - ['/root', '/root/a', '/root/a/x', '/root/b', '/root/c'], - ); -}); - -test('listFoldersChunked : non-récursif ne remonte que les sous-dossiers immédiats', async () => { - restoreFns.push(fakeSaf()); - const folders = await listFoldersChunked('/root'); - assert.deepEqual( - folders.map((f) => f.uri), - ['/root/a', '/root/b', '/root/c'], - ); -}); - -test('listFoldersChunked : includeRoot=false ne retourne pas la racine', async () => { - restoreFns.push(fakeSaf()); - const folders = await listFoldersChunked('/root', { recursive: true }); - assert.deepEqual( - folders.map((f) => f.uri), - ['/root/a', '/root/a/x', '/root/b', '/root/c'], - ); -}); - -test('listFoldersChunked : cède au event loop (budget 0 → yield par dossier)', async () => { - restoreFns.push(fakeSaf()); - let yields = 0; - restoreFns.push( - __setSafWalkForTests({ - yield: async () => { - yields++; - }, - }), - ); - - const folders = await listFoldersChunked('/root', { recursive: true }, 0); - assert.deepEqual( - folders.map((f) => f.uri), - ['/root/a', '/root/a/x', '/root/b', '/root/c'], - ); - assert.ok(yields >= 5, `expected at least one yield per folder, got ${yields}`); -}); - -test('listFoldersChunked : budget maximum → aucun yield, résultat inchangé', async () => { - restoreFns.push(fakeSaf()); - let yields = 0; - restoreFns.push( - __setSafWalkForTests({ - yield: async () => { - yields++; - }, - }), - ); - - const folders = await listFoldersChunked('/root', { recursive: true }, Number.MAX_SAFE_INTEGER); - assert.deepEqual( - folders.map((f) => f.uri), - ['/root/a', '/root/a/x', '/root/b', '/root/c'], - ); - assert.equal(yields, 0); -}); - -test('__setSafWalkForTests : la restauration rend l’implémentation d’origine', async () => { - restoreFns.push(fakeSaf()); - const restore = __setSafWalkForTests({ list: () => [dir('/fake')] }); - restore(); - - let listed = false; - restoreFns.push( - __setSafWalkForTests({ - list: (uri) => { - listed = true; - return tree[uri] ?? []; - }, - }), - ); - await listFoldersChunked('/root', { recursive: true }); - assert.equal(listed, true); -}); \ No newline at end of file diff --git a/mobile/tests/syncDevice.test.ts b/mobile/tests/syncDevice.test.ts deleted file mode 100644 index e0cfa9a..0000000 --- a/mobile/tests/syncDevice.test.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { test, beforeEach, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import Database from 'better-sqlite3'; -import { MIGRATIONS, type MigrationDb } from '../services/db/migrations'; -import type { DbSession } from '../services/db/session'; -import { __setDbForTests } from '../services/db/session'; -import { __setWithTransactionForTests } from '../services/db/client'; -import { __setSafWalkForTests } from '../services/safWalk'; -import type { DirectoryEntry, Folder } from '../services/safDirectory.types'; -import { syncRoot, syncDevice } from '../features/syncDevice'; -import { saveFolder, getFolders, getFolderFolders, getFiles } from '../services/db'; - -type Harness = MigrationDb & DbSession; - -let h: Harness; -let restoreFns: Array<() => void> = []; - -function dir(uri: string): Folder { - return { uri, name: uri.split('/').pop() ?? uri, isDirectory: true, exists: true }; -} - -function file(uri: string): DirectoryEntry { - return { - uri, - name: uri.split('/').pop() ?? uri, - isDirectory: false, - extension: '', - exists: true, - size: 1, - type: 'text/plain', - lastModified: 0, - }; -} - -function info(uri: string): { uri: string; name: string; exists: boolean } { - return { uri, name: uri.split('/').pop() ?? uri, exists: true }; -} - -const tree: Record = { - '/root': [dir('/root/a'), dir('/root/b'), file('/root/f1.txt')], - '/root/a': [dir('/root/a/x'), file('/root/a/f2.txt')], - '/root/a/x': [], - '/root/b': [file('/root/b/f3.txt')], - '/rootA': [dir('/rootA/sub')], - '/rootA/sub': [], - '/rootB': [file('/rootB/fB.txt')], -}; - -function createHarness(): Harness { - const sqlite = new Database(':memory:'); - const harness: Harness = { - execAsync: async (sql: string) => { - sqlite.exec(sql); - }, - runAsync: async (sql: string, ...params: unknown[]) => { - sqlite.prepare(sql).run(...params); - }, - getFirstAsync: async (sql: string, ...params: unknown[]) => - (sqlite.prepare(sql).get(...params) ?? null) as never, - getAllAsync: async (sql: string, ...params: unknown[]) => - sqlite.prepare(sql).all(...params) as never, - withExclusiveTransactionAsync: async (task: (txn: Harness) => Promise) => { - sqlite.exec('BEGIN'); - try { - await task(harness); - sqlite.exec('COMMIT'); - } catch (error) { - sqlite.exec('ROLLBACK'); - throw error; - } - }, - }; - return harness; -} - -function fakeSaf(): () => void { - return __setSafWalkForTests({ - list: (uri) => tree[uri] ?? [], - info, - }); -} - -async function waitUntil(condition: () => boolean, timeoutMs = 2000): Promise { - const start = Date.now(); - while (!condition()) { - if (Date.now() - start >= timeoutMs) throw new Error('waitUntil: timeout'); - await new Promise((resolve) => setTimeout(resolve, 5)); - } -} - -beforeEach(async () => { - h = createHarness(); - for (const migration of MIGRATIONS) { - await migration.up(h); - } - __setDbForTests(h); - __setWithTransactionForTests(async (work) => work({} as never)); - restoreFns.push(fakeSaf()); -}); - -afterEach(() => { - __setDbForTests(null); - __setWithTransactionForTests(null); - while (restoreFns.length) restoreFns.pop()?.(); -}); - -test('walk complet : comptes folders/files/missing, parent_resource_id cohérent, exists=1', async () => { - const root = await saveFolder({ uri: '/root', name: 'Root' }); - - const result = await syncRoot(root.resource_id); - - assert.deepEqual(result, { rootUri: '/root', folders: 4, files: 3, missing: 0 }); - - const stored = await getFiles(); - assert.equal(stored.length, 3); - for (const f of stored) { - assert.equal(f.exists, true); - } - - const subs = await getFolderFolders(root.resource_id); - assert.equal(subs.length, 2); - const a = subs.find((f) => f.name === 'a')!; - assert.equal(a.parent_resource_id, root.resource_id); - assert.equal(a.exists, true); - - const deep = await getFolderFolders(a.resource_id); - assert.equal(deep.length, 1); - assert.equal(deep[0].name, 'x'); - assert.equal(deep[0].parent_resource_id, a.resource_id); - assert.equal(deep[0].exists, true); -}); - -test('single-flight : un second walk n’est pas lancé tant que le premier est en cours', async () => { - const rootA = await saveFolder({ uri: '/rootA', name: 'RootA' }); - const rootB = await saveFolder({ uri: '/rootB', name: 'RootB' }); - - let release!: () => void; - const gate = new Promise((resolve) => { - release = resolve; - }); - let enteredYield = false; - let listingsB = 0; - - restoreFns.push( - __setSafWalkForTests({ - list: (uri) => { - if (uri.startsWith('/rootA') && !enteredYield) { - // Prolonge le walk de A pour dépasser le budget de 16ms et atteindre - // son premier `yield` (qui bloque sur la gate) — sinon A se termine - // trop vite et la sérialisation ne peut pas être observée. - const until = Date.now() + 20; - while (Date.now() < until) {} - } - if (uri.startsWith('/rootB')) listingsB++; - return tree[uri] ?? []; - }, - yield: async () => { - enteredYield = true; - await gate; - }, - info, - }), - ); - - const pA = syncRoot(rootA.resource_id); - await waitUntil(() => enteredYield); - - const pB = syncRoot(rootB.resource_id); - await new Promise((resolve) => setTimeout(resolve, 10)); - assert.equal(listingsB, 0, 'pB doit attendre la fin de pA (sérialisation des walks)'); - - release(); - const [rA, rB] = await Promise.all([pA, pB]); - - assert.equal(rA.folders, 2); - assert.equal(rB.folders, 1); - assert.ok(listingsB > 0, 'pB doit avoir walké après la libération'); -}); - -test('toute la phase de listing SAF précède la moindre écriture DB', async () => { - const root = await saveFolder({ uri: '/root', name: 'Root' }); - - let txStarted = false; - __setWithTransactionForTests(async (work) => { - txStarted = true; - return work({} as never); - }); - - let listCount = 0; - restoreFns.push( - __setSafWalkForTests({ - list: (uri) => { - assert.equal(txStarted, false, `écriture DB pendant le listing SAF (liste ${uri})`); - listCount++; - return tree[uri] ?? []; - }, - info, - }), - ); - - const result = await syncRoot(root.resource_id); - - assert.ok(listCount >= 4, `listing complet attendu, obtenu ${listCount}`); - assert.equal(result.folders, 4); - assert.equal(result.files, 3); -}); - -test('walk répété : idempotent, pas de doublon ni SQLITE_CONSTRAINT', async () => { - const root = await saveFolder({ uri: '/root', name: 'Root' }); - await syncRoot(root.resource_id); - - const foldersBefore = new Map((await getFolders()).map((f) => [f.uri, f.resource_id])); - const filesUriBefore = new Set((await getFiles()).map((f) => f.uri)); - - await syncRoot(root.resource_id); - - const foldersAfter = await getFolders(); - assert.equal(foldersAfter.length, foldersBefore.size); - for (const f of foldersAfter) { - assert.equal(foldersBefore.get(f.uri), f.resource_id, `resource_id stable pour ${f.uri}`); - } - - const filesAfter = await getFiles(); - assert.equal(filesAfter.length, filesUriBefore.size); - for (const f of filesAfter) { - assert.ok(filesUriBefore.has(f.uri)); - assert.equal(f.exists, true); - } -}); - -test('réconciliation : un fichier retiré du SAF passe à exists=0 et compte en missing', async () => { - const root = await saveFolder({ uri: '/root', name: 'Root' }); - await syncRoot(root.resource_id); - assert.equal((await getFiles()).length, 3); - - restoreFns.push( - __setSafWalkForTests({ - list: (uri) => - uri === '/root' - ? (tree['/root'] ?? []).filter((e) => e.uri !== '/root/f1.txt') - : (tree[uri] ?? []), - info, - }), - ); - - const result = await syncRoot(root.resource_id); - - assert.equal(result.missing, 1); - const f1 = (await getFiles()).find((f) => f.uri === '/root/f1.txt')!; - assert.equal(f1.exists, false); - const remaining = (await getFiles()).filter((f) => f.exists); - assert.equal(remaining.length, 2); -}); - -test('syncDevice marche toutes les roots présentes', async () => { - const rootA = await saveFolder({ uri: '/rootA', name: 'RootA' }); - await saveFolder({ uri: '/rootB', name: 'RootB' }); - assert.equal(rootA.parent_resource_id, null); - - const results = await syncDevice(); - - assert.equal(results.length, 2); - const byRoot = new Map(results.map((r) => [r.rootUri, r])); - assert.deepEqual(byRoot.get('/rootA'), { - rootUri: '/rootA', - folders: 2, - files: 0, - missing: 0, - }); - assert.deepEqual(byRoot.get('/rootB'), { - rootUri: '/rootB', - folders: 1, - files: 1, - missing: 0, - }); -}); \ No newline at end of file diff --git a/mobile/tests/syncOutbox.test.ts b/mobile/tests/syncOutbox.test.ts deleted file mode 100644 index 123f48d..0000000 --- a/mobile/tests/syncOutbox.test.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { test, beforeEach, afterEach } from 'node:test'; -import assert from 'node:assert/strict'; -import Database from 'better-sqlite3'; -import { MIGRATIONS, type MigrationDb } from '../services/db/migrations'; -import type { DbSession } from '../services/db/session'; -import { __setDbForTests } from '../services/db/session'; -import { api, setAuthToken } from '../api/client'; -import { - enqueuePendingOperation, - getActiveUserId, - getPendingOperations, - getResourcePermission, - listQueuedOperations, - markPendingOperation, - scheduleRetries, - setActiveUserId, - MAX_PENDING_ATTEMPTS, -} from '../services/db'; -import { - pushPendingOps, - refreshPermissions, - resetPermissionCachedAtForTests, -} from '../features/syncOutbox'; - -import type { PendingOperationType } from '../services/db/types'; - -type Harness = MigrationDb & DbSession; - -let h: Harness; - -function createHarness(): Harness { - const sqlite = new Database(':memory:'); - const harness: Harness = { - execAsync: async (sql: string) => { - sqlite.exec(sql); - }, - runAsync: async (sql: string, ...params: unknown[]) => { - sqlite.prepare(sql).run(...params); - }, - getFirstAsync: async (sql: string, ...params: unknown[]) => - (sqlite.prepare(sql).get(...params) ?? null) as never, - getAllAsync: async (sql: string, ...params: unknown[]) => - sqlite.prepare(sql).all(...params) as never, - withExclusiveTransactionAsync: async (task: (txn: Harness) => Promise) => { - sqlite.exec('BEGIN'); - try { - await task(harness); - sqlite.exec('COMMIT'); - } catch (error) { - sqlite.exec('ROLLBACK'); - throw error; - } - }, - }; - return harness; -} - -function jsonResponse(status: number, body: unknown): Response { - return new Response(JSON.stringify(body), { - status, - headers: { 'Content-Type': 'application/json' }, - }); -} - -function ok(data: unknown): Response { - return jsonResponse(200, { data }); -} - -let calls: { url: string; init: RequestInit }[] = []; -const originalFetch = globalThis.fetch; - -function stubFetch(handler: (url: string, init: RequestInit) => Promise | Response): void { - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { - const initSafe = init ?? {}; - calls.push({ url: String(input), init: initSafe }); - return handler(String(input), initSafe); - }; -} - -function pathOf(url: string): string { - const u = new URL(url); - return u.pathname + u.search; -} - -beforeEach(async () => { - h = createHarness(); - for (const migration of MIGRATIONS) { - await migration.up(h); - } - __setDbForTests(h); - calls = []; - setAuthToken('v4.local.test-token'); - resetPermissionCachedAtForTests(); - stubFetch(() => ok({ applied: 0, failed: null })); -}); - -afterEach(() => { - __setDbForTests(null); - globalThis.fetch = originalFetch; -}); - -async function enqueue(op: { - operation: PendingOperationType; - resourceId?: string; - payload?: Record; -}) { - return enqueuePendingOperation({ - resourceId: op.resourceId ?? 'a'.repeat(32), - resourceType: 'folder', - refType: 'resource', - refId: 7, - operation: op.operation, - payload: op.payload ?? { name: 'Dossier' }, - }); -} - -test('sans token : aucun push, aucune requête réseau', async () => { - setAuthToken(null); - await enqueue({ operation: 'create_resource' }); - const result = await pushPendingOps(); - assert.deepEqual(result, { pushed: 0, retried: 0 }); - assert.equal(calls.length, 0); -}); - -test('queue vide : aucune requête réseau', async () => { - const result = await pushPendingOps(); - assert.deepEqual(result, { pushed: 0, retried: 0 }); - assert.equal(calls.length, 0); -}); - -test('succès complet : toutes les ops passent à completed, body conforme', async () => { - const id1 = await enqueue({ operation: 'create_resource', payload: { name: 'A' } }); - const id2 = await enqueue({ operation: 'create_resource', payload: { name: 'B' } }); - stubFetch(() => ok({ applied: 2, failed: null })); - - const result = await pushPendingOps(); - - assert.deepEqual(result, { pushed: 2, retried: 0 }); - assert.equal(pathOf(calls[0].url), '/api/v1/sync/ops'); - assert.equal(calls[0].init.method, 'POST'); - const body = JSON.parse(String(calls[0].init.body)); - assert.equal(body.operations.length, 2); - assert.equal(body.operations[0].operation_id, id1); - assert.equal(body.operations[0].resource_type, 'folder'); - assert.deepEqual(body.operations[0].payload, { name: 'A' }); - assert.equal(body.operations[1].operation_id, id2); - - const ops = await getPendingOperations(); - const byId = new Map(ops.map((op) => [op.id, op])); - assert.equal(byId.get(id1)?.status, 'completed'); - assert.equal(byId.get(id2)?.status, 'completed'); - assert.equal(byId.get(id1)?.attempts, 0); -}); - -test('succès partiel : reprise exacte à l’index applied (les suivantes intactes)', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - const id2 = await enqueue({ operation: 'create_resource' }); - const id3 = await enqueue({ operation: 'create_resource' }); - stubFetch(() => - ok({ - applied: 1, - failed: { operation_id: id2, code: 'NAME_CONFLICT', message: 'exists déjà' }, - }), - ); - - const result = await pushPendingOps(); - - assert.deepEqual(result, { pushed: 1, retried: 0 }); - const ops = await getPendingOperations(); - const byId = new Map(ops.map((op) => [op.id, op])); - assert.equal(byId.get(id1)?.status, 'completed'); - assert.equal(byId.get(id2)?.status, 'pending'); - assert.equal(byId.get(id2)?.attempts, 1); // backoff programmé, par dead-letter - assert.ok((byId.get(id2)?.nextRetryAt ?? 0) > 0); - assert.match(byId.get(id2)?.error ?? '', /NAME_CONFLICT/); - assert.equal(byId.get(id3)?.status, 'pending'); - assert.equal(byId.get(id3)?.attempts, 0); // jamais soumise → intacte -}); - -test('applied = 0 + échec : rien commité, pas de dead-letter prématurée', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - stubFetch(() => - ok({ applied: 0, failed: { operation_id: id1, code: 'INVALID_REQUEST', message: 'payload invalide' } }), - ); - - const result = await pushPendingOps(); - - assert.deepEqual(result, { pushed: 0, retried: 0 }); - const [op] = await getPendingOperations(); - assert.equal(op.status, 'pending'); - assert.equal(op.attempts, 1); // < MAX_PENDING_ATTEMPTS, le cycle recommence - assert.ok((op.nextRetryAt ?? 0) > 0); -}); - -test('échec transitoire (fetch rejette) : retrié sans incrémenter attempts', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - const id2 = await enqueue({ operation: 'create_resource' }); - stubFetch(() => { - throw new Error('network unreachable'); - }); - - const result = await pushPendingOps(); - - assert.deepEqual(result, { pushed: 0, retried: 2 }); - const ops = await getPendingOperations(); - for (const op of ops) { - assert.equal(op.status, 'pending'); - assert.equal(op.attempts, 0); - assert.ok((op.nextRetryAt ?? 0) > Date.now()); - } -}); - -test('échec transitoire 5xx : idem (HTTP_500 → retry, pas de dead-letter)', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - stubFetch(() => jsonResponse(500, { error: { code: 'INTERNAL', message: 'boom' } })); - - const result = await pushPendingOps(); - - assert.deepEqual(result, { pushed: 0, retried: 1 }); - const [op] = await getPendingOperations(); - assert.equal(op.status, 'pending'); - assert.equal(op.attempts, 0); -}); - -test('refus terminal répété : dead-letter après MAX_PENDING_ATTEMPTS', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - stubFetch(() => - ok({ applied: 0, failed: { operation_id: id1, code: 'NAME_CONFLICT', message: 'dup' } }), - ); - - for (let i = 0; i < MAX_PENDING_ATTEMPTS; i++) { - await pushPendingOps(); - // le backoff repousse next_retry_at : on force le reset pour re-soumettre - await h.runAsync('UPDATE pending_operations SET next_retry_at = NULL'); - } - - const [op] = await getPendingOperations(); - assert.equal(op.status, 'failed'); - assert.equal(op.attempts, MAX_PENDING_ATTEMPTS); - assert.equal(op.nextRetryAt, null); // plus jamais rejouée -}); - -test('listQueuedOperations honore le tri FIFO et le LIMIT', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - const id2 = await enqueue({ operation: 'create_resource' }); - const id3 = await enqueue({ operation: 'create_resource' }); - - const first = await listQueuedOperations(2); - assert.deepEqual(first.map((op) => op.id), [id1, id2]); - - const later = Date.now() + 60_000; - await scheduleRetries([id2], later); - const due = await listQueuedOperations(10); - assert.deepEqual(due.map((op) => op.id), [id1, id3]); // id2 pas encore due -}); - -test('scheduleRetries ne change pas attempts (différent de markPendingOperation failed)', async () => { - const id1 = await enqueue({ operation: 'create_resource' }); - await scheduleRetries([id1], Date.now() + 1); - const [op] = await getPendingOperations(); - assert.equal(op.attempts, 0); - assert.ok((op.nextRetryAt ?? 0) > 0); -}); - -test('refreshPermissions : delta après = max cachedAt du snapshot', async () => { - const perms = [ - { - resource_id: 'a'.repeat(32), - resourceType: 'folder', - effectiveAccess: 'owner', - inherit: true, - ownerId: 'u1', - sharedById: null, - expiresAt: null, - cachedAt: 1000, - updatedAt: 1000, - }, - { - resource_id: 'b'.repeat(32), - resourceType: 'file', - effectiveAccess: 'viewer', - inherit: false, - ownerId: 'u2', - sharedById: 'u1', - expiresAt: 5, - cachedAt: 2000, - updatedAt: 2000, - }, - ]; - stubFetch((url) => (new URL(url).searchParams.get('after') === null ? ok(perms) : ok([]))); - - const n1 = await refreshPermissions(); - assert.equal(n1, 2); - assert.equal(pathOf(calls[0].url), '/api/v1/sync/permissions'); - assert.equal((await getResourcePermission('a'.repeat(32), 'folder'))?.effectiveAccess, 'owner'); - assert.equal((await getResourcePermission('b'.repeat(32), 'file'))?.expiresAt, 5); - - // 2e tick (snapshot vide) → after = max cachedAt du 1er - const n2 = await refreshPermissions(); - assert.equal(n2, 0); - assert.equal(pathOf(calls[1].url), '/api/v1/sync/permissions?after=2000'); -}); - -test('refreshPermissions garde la référence dans le temps pour after', async () => { - const first = { - resource_id: 'c'.repeat(32), - resourceType: 'folder', - effectiveAccess: 'editor', - inherit: true, - ownerId: 'u1', - sharedById: null, - expiresAt: null, - cachedAt: 42, - updatedAt: 42, - }; - const second = { - resource_id: 'd'.repeat(32), - resourceType: 'folder', - effectiveAccess: 'viewer', - inherit: false, - ownerId: 'u3', - sharedById: 'u1', - expiresAt: null, - cachedAt: 84, - updatedAt: 84, - }; - stubFetch((url) => { - const after = new URL(url).searchParams.get('after'); - return ok(after === null ? [first] : [second]); - }); - - await refreshPermissions(); - await refreshPermissions(); - assert.equal((await getResourcePermission('d'.repeat(32), 'folder'))?.effectiveAccess, 'viewer'); - assert.equal(pathOf(calls[1].url), '/api/v1/sync/permissions?after=42'); -}); - -test('outbox scopée par compte : seul le compte actif voit/pousse ses ops', async () => { - await setActiveUserId('u1'); - const idA = await enqueue({ operation: 'create_resource', payload: { name: 'A' } }); - await setActiveUserId('u2'); - const idB = await enqueue({ operation: 'create_resource', payload: { name: 'B' } }); - - assert.equal(await getActiveUserId(), 'u2'); - const visibleAsU2 = (await getPendingOperations()).map((op) => op.id); - assert.deepEqual(visibleAsU2, [idB], 'u2 ne voit que ses opérations'); - - await setActiveUserId('u1'); - assert.deepEqual( - (await getPendingOperations()).map((op) => op.id), - [idA], - 'u1 ne voit que ses opérations', - ); - - stubFetch(() => ok({ applied: 1, failed: null })); - await setActiveUserId('u2'); - const result = await pushPendingOps(); - assert.deepEqual(result, { pushed: 1, retried: 0 }); - const body = JSON.parse(String(calls[0].init.body)); - assert.equal(body.operations.length, 1, 'u2 ne pousse pas les opérations de u1'); - assert.equal(body.operations[0].operation_id, idB); - - await setActiveUserId('u1'); - const leftover = await getPendingOperations(); - assert.deepEqual(leftover.map((op) => op.id), [idA], 'l’op u1 reste intacte et non-poussée'); -}); - -test('permissions scopées par compte : snapshot et miroir par compte', async () => { - const permA = { - resource_id: 'a'.repeat(32), - resourceType: 'folder', - effectiveAccess: 'owner', - inherit: true, - ownerId: 'u1', - sharedById: null, - expiresAt: null, - cachedAt: 1000, - updatedAt: 1000, - }; - - await setActiveUserId('u1'); - stubFetch((url) => (new URL(url).searchParams.get('after') === null ? ok([permA]) : ok([]))); - await refreshPermissions(); - assert.equal( - (await getResourcePermission('a'.repeat(32), 'folder'))?.effectiveAccess, - 'owner', - 'u1 voit la permission de son snapshot', - ); - - // u2 n'a pas encore de snapshot : pas d'`after` (repart du début) et la - // permission de u1 n'est pas visible tant que son propre snapshot n'est pas - // arrivé. - await setActiveUserId('u2'); - assert.equal(await getResourcePermission('a'.repeat(32), 'folder'), null); - await refreshPermissions(); - assert.equal(pathOf(calls[1].url), '/api/v1/sync/permissions', 'u2 repart sans after'); - assert.equal( - (await getResourcePermission('a'.repeat(32), 'folder'))?.effectiveAccess, - 'owner', - 'u2 voit sa propre copie après son snapshot', - ); -}); \ No newline at end of file diff --git a/mobile/tsconfig.json b/mobile/tsconfig.json deleted file mode 100644 index b9567f6..0000000 --- a/mobile/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "expo/tsconfig.base", - "compilerOptions": { - "strict": true - } -}