From 97ca8fa84652ad8fd9bcb9048540f37f4dcf86ad Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 08:21:52 -0500 Subject: [PATCH 01/15] chore: format with prettier --- mobile/README.md | 22 +- mobile/app.config.ts | 2 +- mobile/app/(admin)/_layout.tsx | 32 +- mobile/app/(admin)/analytics/page.tsx | 2 +- mobile/app/(admin)/create/page.tsx | 124 +-- mobile/app/(admin)/index.tsx | 323 +++--- mobile/app/(admin)/reviews/[eventId].tsx | 149 +-- mobile/app/(admin)/reviews/_layout.tsx | 2 +- mobile/app/(admin)/settings/page.tsx | 2 +- mobile/app/(auth)/_layout.tsx | 19 +- mobile/app/(auth)/sign-in.tsx | 24 +- mobile/app/(auth)/sign-up.tsx | 47 +- mobile/app/(onboarding)/_layout.tsx | 2 +- mobile/app/(onboarding)/onboarding.tsx | 94 +- mobile/app/(shared)/_layout.tsx | 2 +- mobile/app/(shared)/settings/_layout.tsx | 4 +- .../app/(shared)/settings/edit-location.tsx | 74 +- mobile/app/(shared)/settings/edit-name.tsx | 57 +- mobile/app/(student)/_layout.tsx | 21 +- mobile/app/(student)/feedback.tsx | 667 ++++++------ mobile/app/(student)/index.tsx | 134 +-- mobile/app/(student)/settings/faq.tsx | 15 +- mobile/app/(student)/settings/page.tsx | 2 +- mobile/app/_layout.tsx | 5 +- mobile/app/welcome.tsx | 26 +- mobile/src/components/CoreImpactMetrics.tsx | 197 ++-- mobile/src/components/CustomButton.tsx | 2 +- mobile/src/components/CustomInput.tsx | 2 +- mobile/src/components/EventCard.tsx | 384 ++++--- mobile/src/components/EventEditorModal.tsx | 960 ++++++++++-------- .../src/components/LocationFilterDropdown.tsx | 8 +- .../src/components/NotificationsListener.tsx | 18 +- mobile/src/components/NotificationsToggle.tsx | 14 +- mobile/src/components/PushTokenRegistrar.tsx | 2 +- mobile/src/components/SettingsScreen.tsx | 298 ++++-- mobile/src/components/SignInWith.tsx | 28 +- mobile/src/components/SortDropdown.tsx | 153 +-- mobile/src/content/terms.ts | 17 +- mobile/src/hooks/useCountdown.ts | 8 +- mobile/src/hooks/useEvents.ts | 24 +- mobile/src/lib/ThemeProvider.tsx | 13 +- mobile/src/lib/constants.ts | 1 - mobile/src/lib/firebase/config.ts | 19 +- mobile/src/lib/firebase/events.ts | 4 +- mobile/src/lib/firebase/users.ts | 21 +- mobile/src/lib/notifications.ts | 22 +- mobile/src/lib/rbacClient.tsx | 36 +- mobile/src/lib/schemas/events.schema.ts | 98 +- mobile/src/lib/schemas/reviews.schema.ts | 2 +- mobile/src/lib/schemas/users.schema.ts | 14 +- mobile/src/lib/theme.ts | 4 +- mobile/src/lib/time.ts | 34 +- mobile/src/lib/utils.ts | 11 +- mobile/tests/summary.md | 56 - mobile/tsconfig.json | 18 +- 55 files changed, 2450 insertions(+), 1869 deletions(-) delete mode 100644 mobile/tests/summary.md diff --git a/mobile/README.md b/mobile/README.md index be0c8ea..8396a3d 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -3,6 +3,7 @@ Mobile app for the BU Catering Leftovers project. Built with Expo + React Native. ## Prerequisites + - Node 18+ and npm - Expo CLI (npx expo) - Firebase CLI (npm i -g firebase-tools) @@ -10,7 +11,9 @@ Mobile app for the BU Catering Leftovers project. Built with Expo + React Native ## Setup Instructions ### Step 1: Install Dependencies + **Location:** Run from repo root (se-bu-catering-leftovers/) + ```bash npm install --legacy-peer-deps cd mobile @@ -19,13 +22,16 @@ cd .. # Go back to repo root ``` ### Step 2: Environment Variables + **Location:** Create file in mobile/ folder + ```bash # Create the file touch mobile/.env.local ``` **File content:** Copy these keys (same as web app): + ```bash NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key_here NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain_here @@ -37,45 +43,59 @@ NEXT_PUBLIC_FIREBASE_DATABASE_URL=your_database_url_here ⚠️ **Do not commit real values to Git** ### Step 3: Start Firestore Emulator + **Location:** Run from repo root (se-bu-catering-leftovers/) + ```bash npm run emulators ``` + ✅ Keep this terminal open - emulator must stay running ### Step 4: Seed Sample Data (First Time Only) + **Location:** Run from repo root (se-bu-catering-leftovers/) in a NEW terminal + ```bash npm run seed ``` + ✅ This creates sample Users, Events, and Reviews ✅ Data persists in .firebase-data/ folder ✅ Only run once unless you want fresh data ### Step 5: Run the Mobile App + **Location:** Run from mobile/ folder in a NEW terminal + ```bash cd mobile npx expo start ``` + ✅ This opens Expo development server ✅ Scan QR code with Expo Go app on your phone ## Development Commands ### Format Code + **Location:** Run from repo root (se-bu-catering-leftovers/) + ```bash npm run format ``` ## Terminal Setup Summary + You'll need 3 terminals open: + 1. **Terminal 1:** `npm run emulators` (from repo root) 2. **Terminal 2:** `npm run seed` (from repo root, one-time only) 3. **Terminal 3:** `cd mobile && npx expo start` (from mobile folder) ## Troubleshooting + - If emulator fails: Make sure Firebase CLI is installed globally - If mobile app fails: Make sure .env.local exists in mobile/ folder -- If data missing: Run `npm run seed` again \ No newline at end of file +- If data missing: Run `npm run seed` again diff --git a/mobile/app.config.ts b/mobile/app.config.ts index 8cbb15e..1ca372f 100644 --- a/mobile/app.config.ts +++ b/mobile/app.config.ts @@ -33,7 +33,7 @@ export default ({ config }: ConfigContext): ExpoConfig => { projectId: process.env.EXPO_PUBLIC_EAS_PROJECT_ID ?? '', }, useEmulators: true, - emulatorHost: '127.0.0.1' + emulatorHost: '127.0.0.1', }, plugins: [ 'expo-router', diff --git a/mobile/app/(admin)/_layout.tsx b/mobile/app/(admin)/_layout.tsx index 9667fbd..f25f91f 100644 --- a/mobile/app/(admin)/_layout.tsx +++ b/mobile/app/(admin)/_layout.tsx @@ -30,7 +30,7 @@ export default function AdminLayout() { const userData = userSnap.data(); console.log('Admin: agreedToTerms =', userData.agreedToTerms); - + if (userData.agreedToTerms !== true) { console.log('Admin: Needs to complete onboarding'); setNeedsOnboarding(true); @@ -49,24 +49,34 @@ export default function AdminLayout() { if (!authLoaded || !userLoaded || needsOnboarding === null) { return ( - + ); } if (!isSignedIn) { - return ; + return ; } if (needsOnboarding) { - return ; + return ; } const userRole = user?.publicMetadata?.role as string | undefined; - const userStatus = (user?.publicMetadata as any)?.status as string | undefined; + const userStatus = (user?.publicMetadata as any)?.status as + | string + | undefined; - const isAdmin = userRole === 'admin' && (userStatus === 'active' || userStatus == null); + const isAdmin = + userRole === 'admin' && (userStatus === 'active' || userStatus == null); const isActiveStaff = userRole === 'staff' && userStatus === 'active'; // Only admins and *active* staff can stay in the (admin) stack @@ -74,9 +84,9 @@ export default function AdminLayout() { console.log( 'Admin layout - Redirecting to student route, role/status was:', userRole, - userStatus + userStatus, ); - return ; + return ; } return ( @@ -86,8 +96,8 @@ export default function AdminLayout() { tabBarActiveTintColor: colors.primary, tabBarInactiveTintColor: colors.text.secondary, tabBarStyle: { - backgroundColor: colors.surface, - borderTopColor: colors.border.default, + backgroundColor: colors.surface, + borderTopColor: colors.border.default, height: 70, paddingBottom: 8, paddingTop: 8, @@ -139,4 +149,4 @@ export default function AdminLayout() { /> ); -} \ No newline at end of file +} diff --git a/mobile/app/(admin)/analytics/page.tsx b/mobile/app/(admin)/analytics/page.tsx index 9f89757..3cf6284 100644 --- a/mobile/app/(admin)/analytics/page.tsx +++ b/mobile/app/(admin)/analytics/page.tsx @@ -36,7 +36,7 @@ export default function AnalyticsScreen() { marginTop: spacing.lg, }, }), - [colors] + [colors], ); const role = diff --git a/mobile/app/(admin)/create/page.tsx b/mobile/app/(admin)/create/page.tsx index 0ba9339..52520e1 100644 --- a/mobile/app/(admin)/create/page.tsx +++ b/mobile/app/(admin)/create/page.tsx @@ -35,7 +35,7 @@ export default function CreateEventScreen() { token, 'Catering Leftovers', `${eventData.name ?? 'Event'} created at ${loc}`, - { eventId } + { eventId }, ); } } @@ -44,7 +44,7 @@ export default function CreateEventScreen() { await notifyStudents( 'Catering Leftovers', `${eventData.name ?? 'An event'} • ${loc} • Tap for details.`, - { eventId } + { eventId }, ); } @@ -55,60 +55,64 @@ export default function CreateEventScreen() { } }; - const styles = React.useMemo(() => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.background, - padding: spacing.lg, - paddingTop: spacing.xxl + 20, - }, - title: { - ...typography.h3, - color: colors.text.primary, - marginBottom: spacing.sm, - }, - subtitle: { - ...typography.body, - color: colors.text.secondary, - marginBottom: spacing.xl, - }, - content: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - gap: spacing.xl, - }, - instructionBox: { - backgroundColor: colors.surface, - padding: spacing.lg, - borderRadius: 12, - borderWidth: 1, - borderColor: colors.border.light, - maxWidth: 300, - }, - instructionText: { - ...typography.body, - color: colors.text.secondary, - textAlign: 'center', - lineHeight: 22, - }, - createButton: { - backgroundColor: colors.primary, - paddingHorizontal: spacing.xl, - paddingVertical: spacing.md, - borderRadius: 12, - elevation: 2, - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 4, - }, - createButtonText: { - ...typography.h5, - color: colors.text.onPrimary, - fontWeight: '600', - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + padding: spacing.lg, + paddingTop: spacing.xxl + 20, + }, + title: { + ...typography.h3, + color: colors.text.primary, + marginBottom: spacing.sm, + }, + subtitle: { + ...typography.body, + color: colors.text.secondary, + marginBottom: spacing.xl, + }, + content: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + gap: spacing.xl, + }, + instructionBox: { + backgroundColor: colors.surface, + padding: spacing.lg, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.border.light, + maxWidth: 300, + }, + instructionText: { + ...typography.body, + color: colors.text.secondary, + textAlign: 'center', + lineHeight: 22, + }, + createButton: { + backgroundColor: colors.primary, + paddingHorizontal: spacing.xl, + paddingVertical: spacing.md, + borderRadius: 12, + elevation: 2, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + }, + createButtonText: { + ...typography.h5, + color: colors.text.onPrimary, + fontWeight: '600', + }, + }), + [colors], + ); return ( @@ -117,10 +121,14 @@ export default function CreateEventScreen() { - Tap the button below to create a new event with all the details like location, food items, and timing. + Tap the button below to create a new event with all the details like + location, food items, and timing. - setShowModal(true)}> + setShowModal(true)} + > + Create New Event diff --git a/mobile/app/(admin)/index.tsx b/mobile/app/(admin)/index.tsx index ee3c3b8..8c0f966 100644 --- a/mobile/app/(admin)/index.tsx +++ b/mobile/app/(admin)/index.tsx @@ -1,6 +1,14 @@ // app/(admin)/index.tsx import React, { useState } from 'react'; -import { View, Text, StyleSheet, FlatList, RefreshControl, Pressable, Alert } from 'react-native'; +import { + View, + Text, + StyleSheet, + FlatList, + RefreshControl, + Pressable, + Alert, +} from 'react-native'; import { useUser } from '@clerk/clerk-expo'; import { useTheme } from '../../src/lib/ThemeProvider'; import { typography, spacing, borderRadius } from '../../src/lib/theme'; @@ -35,7 +43,7 @@ export default function AdminHomeScreen() { const handleSave = async (updates: Partial) => { if (!editingEvent?.id) return; const prevStatus = editingEvent.status; - const nextStatus = (updates.status ?? editingEvent.status); + const nextStatus = updates.status ?? editingEvent.status; const becameOpen = prevStatus !== 'open' && nextStatus === 'open'; try { @@ -49,7 +57,9 @@ export default function AdminHomeScreen() { updates.locationDetails ?? editingEvent.locationDetails, ].filter(Boolean) as string[]; const loc = locBits.join(' • ') || 'BU Campus'; - await notifyStudents('New food available', `${name} • ${loc}`, { eventId: editingEvent.id }); + await notifyStudents('New food available', `${name} • ${loc}`, { + eventId: editingEvent.id, + }); } Alert.alert('Success', 'Event updated successfully'); @@ -66,130 +76,147 @@ export default function AdminHomeScreen() { if (activeTab === 'open') { return event.status === 'open'; } else { - return event.status === 'closed' || event.status === 'drafted' || event.status === 'saved'; + return ( + event.status === 'closed' || + event.status === 'drafted' || + event.status === 'saved' + ); } }); const filteredEvents = React.useMemo(() => { if (!selectedSort) return filteredEventsBase; - const withExpiry = filteredEventsBase.map(e => ({ e, expiry: getExpiryMs({ foodAvailable: e.foodAvailable, duration: e.duration }) })); - const filtered = withExpiry.filter(x => typeof x.expiry === 'number' && x.expiry !== null); + const withExpiry = filteredEventsBase.map((e) => ({ + e, + expiry: getExpiryMs({ + foodAvailable: e.foodAvailable, + duration: e.duration, + }), + })); + const filtered = withExpiry.filter( + (x) => typeof x.expiry === 'number' && x.expiry !== null, + ); filtered.sort((a, b) => { - if (selectedSort === 'expiry-asc') return (a.expiry as number) - (b.expiry as number); + if (selectedSort === 'expiry-asc') + return (a.expiry as number) - (b.expiry as number); return (b.expiry as number) - (a.expiry as number); }); - const missing = withExpiry.filter(x => x.expiry === null); - return [...filtered.map(x => x.e), ...missing.map(x => x.e)]; + const missing = withExpiry.filter((x) => x.expiry === null); + return [...filtered.map((x) => x.e), ...missing.map((x) => x.e)]; }, [filteredEventsBase, selectedSort]); - const styles = React.useMemo(() => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.background, - }, - header: { - padding: spacing.lg, - paddingTop: spacing.xxl + 20, - backgroundColor: colors.surface, - borderBottomWidth: 1, - borderBottomColor: colors.border.light, - }, - titleRow: { - flexDirection: 'row', - alignItems: 'center', - gap: spacing.sm, - }, - greeting: { - ...typography.h4, - color: colors.text.primary, - }, - adminBadge: { - backgroundColor: colors.primary, - paddingHorizontal: spacing.sm, - paddingVertical: 2, - borderRadius: 4, - }, - adminBadgeText: { - ...typography.caption, - color: colors.text.onPrimary, - fontWeight: '700', - }, - subtitle: { - ...typography.bodySmall, - color: colors.text.secondary, - marginTop: spacing.xs, - }, - tabContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: colors.surface, - borderBottomWidth: 1, - borderBottomColor: colors.border.light, - paddingRight: spacing.lg, - }, - tabsLeft: { - flexDirection: 'row', - flex: 1, - }, - tab: { - flex: 1, - paddingVertical: spacing.md, - alignItems: 'center', - borderBottomWidth: 2, - borderBottomColor: 'transparent', - }, - sortRight: { - paddingLeft: spacing.md, - }, - activeTab: { - borderBottomColor: colors.primary, - }, - tabText: { - ...typography.body, - color: colors.text.secondary, - fontWeight: '600', - }, - activeTabText: { - color: colors.primary, - }, - listContent: { - padding: spacing.lg, - }, - emptyState: { - alignItems: 'center', - justifyContent: 'center', - paddingVertical: spacing.xxl * 2, - }, - emptyText: { - ...typography.h5, - color: colors.text.secondary, - textAlign: 'center', - }, - emptySubtext: { - ...typography.body, - color: colors.text.secondary, - textAlign: 'center', - marginTop: spacing.sm, - }, - adminCardWrapper: { - position: 'relative', - }, - statusBadge: { - position: 'absolute', - top: spacing.sm, - right: spacing.sm, - zIndex: 10, - paddingHorizontal: spacing.sm, - paddingVertical: 4, - borderRadius: borderRadius.sm, - }, - statusBadgeText: { - ...typography.caption, - color: colors.text.onPrimary, - fontWeight: '700', - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + padding: spacing.lg, + paddingTop: spacing.xxl + 20, + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderBottomColor: colors.border.light, + }, + titleRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm, + }, + greeting: { + ...typography.h4, + color: colors.text.primary, + }, + adminBadge: { + backgroundColor: colors.primary, + paddingHorizontal: spacing.sm, + paddingVertical: 2, + borderRadius: 4, + }, + adminBadgeText: { + ...typography.caption, + color: colors.text.onPrimary, + fontWeight: '700', + }, + subtitle: { + ...typography.bodySmall, + color: colors.text.secondary, + marginTop: spacing.xs, + }, + tabContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderBottomColor: colors.border.light, + paddingRight: spacing.lg, + }, + tabsLeft: { + flexDirection: 'row', + flex: 1, + }, + tab: { + flex: 1, + paddingVertical: spacing.md, + alignItems: 'center', + borderBottomWidth: 2, + borderBottomColor: 'transparent', + }, + sortRight: { + paddingLeft: spacing.md, + }, + activeTab: { + borderBottomColor: colors.primary, + }, + tabText: { + ...typography.body, + color: colors.text.secondary, + fontWeight: '600', + }, + activeTabText: { + color: colors.primary, + }, + listContent: { + padding: spacing.lg, + }, + emptyState: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.xxl * 2, + }, + emptyText: { + ...typography.h5, + color: colors.text.secondary, + textAlign: 'center', + }, + emptySubtext: { + ...typography.body, + color: colors.text.secondary, + textAlign: 'center', + marginTop: spacing.sm, + }, + adminCardWrapper: { + position: 'relative', + }, + statusBadge: { + position: 'absolute', + top: spacing.sm, + right: spacing.sm, + zIndex: 10, + paddingHorizontal: spacing.sm, + paddingVertical: 4, + borderRadius: borderRadius.sm, + }, + statusBadgeText: { + ...typography.caption, + color: colors.text.onPrimary, + fontWeight: '700', + }, + }), + [colors], + ); return ( @@ -268,8 +295,8 @@ export default function AdminHomeScreen() { {loading ? 'Loading events...' : activeTab === 'open' - ? 'No open events' - : 'No previous events'} + ? 'No open events' + : 'No previous events'} Pull down to refresh @@ -289,36 +316,46 @@ export default function AdminHomeScreen() { ); } -function AdminEventCard({ event, onEdit }: { event: Event; onEdit: (event: Event) => void }) { +function AdminEventCard({ + event, + onEdit, +}: { + event: Event; + onEdit: (event: Event) => void; +}) { const { colors } = useTheme(); const statusColor = event.status === 'open' ? colors.success : event.status === 'closed' - ? colors.text.secondary - : event.status === 'drafted' - ? colors.warning - : colors.primary; + ? colors.text.secondary + : event.status === 'drafted' + ? colors.warning + : colors.primary; - const styles = React.useMemo(() => StyleSheet.create({ - adminCardWrapper: { - position: 'relative', - }, - statusBadge: { - position: 'absolute', - top: spacing.sm, - right: spacing.sm, - zIndex: 10, - paddingHorizontal: spacing.sm, - paddingVertical: 4, - borderRadius: borderRadius.sm, - }, - statusBadgeText: { - ...typography.caption, - color: colors.text.onPrimary, - fontWeight: '700', - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + adminCardWrapper: { + position: 'relative', + }, + statusBadge: { + position: 'absolute', + top: spacing.sm, + right: spacing.sm, + zIndex: 10, + paddingHorizontal: spacing.sm, + paddingVertical: 4, + borderRadius: borderRadius.sm, + }, + statusBadgeText: { + ...typography.caption, + color: colors.text.onPrimary, + fontWeight: '700', + }, + }), + [colors], + ); return ( diff --git a/mobile/app/(admin)/reviews/[eventId].tsx b/mobile/app/(admin)/reviews/[eventId].tsx index e202b64..d080427 100644 --- a/mobile/app/(admin)/reviews/[eventId].tsx +++ b/mobile/app/(admin)/reviews/[eventId].tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback } from 'react'; import { View, Text, @@ -7,23 +7,23 @@ import { Image, ActivityIndicator, Pressable, -} from "react-native"; -import { SafeAreaView } from "react-native-safe-area-context"; -import { useRouter, useLocalSearchParams } from "expo-router"; +} from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { useRouter, useLocalSearchParams } from 'expo-router'; import { collection, query, orderBy, getDocs, onSnapshot, -} from "firebase/firestore"; -import { firestore } from "../../../src/lib/firebase/config"; +} from 'firebase/firestore'; +import { firestore } from '../../../src/lib/firebase/config'; import { colors, typography, spacing, borderRadius, -} from "../../../src/lib/theme"; +} from '../../../src/lib/theme'; interface Review { id?: string; @@ -43,30 +43,39 @@ const StarRating = ({ rating }: { rating: number }) => { stars.push( - + - + , ); } return {stars}; }; export default function AdminEventReviewsPage() { - const { eventId, eventName } = useLocalSearchParams<{ eventId: string; eventName?: string }>(); + const { eventId, eventName } = useLocalSearchParams<{ + eventId: string; + eventName?: string; + }>(); const router = useRouter(); const [reviews, setReviews] = useState([]); const [loading, setLoading] = useState(true); - const averageRating = reviews.length > 0 - ? reviews.reduce((sum, r) => sum + (r.rating || 0), 0) / reviews.length - : 0; + const averageRating = + reviews.length > 0 + ? reviews.reduce((sum, r) => sum + (r.rating || 0), 0) / reviews.length + : 0; const fetchReviews = useCallback(async () => { if (!eventId) return; try { - const reviewsRef = collection(firestore, "Reviews", eventId, "Reviews"); - const q = query(reviewsRef, orderBy("date", "desc")); + const reviewsRef = collection(firestore, 'Reviews', eventId, 'Reviews'); + const q = query(reviewsRef, orderBy('date', 'desc')); const snapshot = await getDocs(q); const list = snapshot.docs.map((doc) => ({ id: doc.id, @@ -75,7 +84,7 @@ export default function AdminEventReviewsPage() { setReviews(list); setLoading(false); } catch (err) { - console.error("Error fetching reviews:", err); + console.error('Error fetching reviews:', err); setLoading(false); } }, [eventId]); @@ -83,14 +92,14 @@ export default function AdminEventReviewsPage() { useEffect(() => { if (!eventId) return; const unsub = onSnapshot( - collection(firestore, "Reviews", eventId, "Reviews"), - () => fetchReviews() + collection(firestore, 'Reviews', eventId, 'Reviews'), + () => fetchReviews(), ); return () => unsub(); }, [eventId, fetchReviews]); const formatDate = (timestamp: any) => { - if (!timestamp) return ""; + if (!timestamp) return ''; const date = new Date(timestamp.seconds * 1000); const now = new Date(); const diffMs = now.getTime() - date.getTime(); @@ -98,20 +107,20 @@ export default function AdminEventReviewsPage() { const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); - if (diffMins < 1) return "Just now"; + if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; - - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: date.getFullYear() !== now.getFullYear() ? "numeric" : undefined + + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, }); }; return ( - + {/* Header */} router.back()} style={styles.backButton}> @@ -122,11 +131,13 @@ export default function AdminEventReviewsPage() { Feedback {eventName && ( - {eventName} + + {eventName} + )} - {reviews.length} {reviews.length === 1 ? "review" : "reviews"} + {reviews.length} {reviews.length === 1 ? 'review' : 'reviews'} {reviews.length > 0 && ( <> @@ -170,13 +181,15 @@ export default function AdminEventReviewsPage() { {review.shareContact && review.email ? review.email.charAt(0).toUpperCase() - : "?"} + : '?'} {review.shareContact ? ( <> - {review.name && {review.name}} + {review.name && ( + {review.name} + )} {review.email} ) : ( @@ -212,9 +225,9 @@ export default function AdminEventReviewsPage() { contentContainerStyle={styles.imageScrollContent} > {review.images.map((url, i) => ( - @@ -237,8 +250,8 @@ const styles = StyleSheet.create({ backgroundColor: colors.background, }, header: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', paddingHorizontal: spacing.lg, paddingVertical: spacing.lg, backgroundColor: colors.surface, @@ -252,19 +265,19 @@ const styles = StyleSheet.create({ backArrow: { fontSize: 28, color: colors.primary, - fontWeight: "600", + fontWeight: '600', }, headerContent: { flex: 1, }, titleRow: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', }, headerTitle: { ...typography.h3, color: colors.text.primary, - fontWeight: "700", + fontWeight: '700', }, eventName: { ...typography.body, @@ -277,8 +290,8 @@ const styles = StyleSheet.create({ color: colors.text.secondary, }, headerSubtitleRow: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', marginTop: 2, }, headerDot: { @@ -287,14 +300,14 @@ const styles = StyleSheet.create({ marginHorizontal: spacing.xs, }, headerRatingContainer: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', gap: spacing.xs, }, headerRatingText: { ...typography.bodySmall, color: colors.text.primary, - fontWeight: "600", + fontWeight: '600', }, container: { flex: 1, @@ -306,12 +319,12 @@ const styles = StyleSheet.create({ }, centerContent: { flex: 1, - justifyContent: "center", - alignItems: "center", + justifyContent: 'center', + alignItems: 'center', paddingVertical: spacing.xl * 3, }, emptyState: { - alignItems: "center", + alignItems: 'center', paddingVertical: spacing.xl * 3, paddingHorizontal: spacing.xl, }, @@ -322,13 +335,13 @@ const styles = StyleSheet.create({ emptyTitle: { ...typography.h4, color: colors.text.primary, - fontWeight: "600", + fontWeight: '600', marginBottom: spacing.sm, }, emptyText: { ...typography.body, color: colors.text.secondary, - textAlign: "center", + textAlign: 'center', lineHeight: 22, }, reviewCard: { @@ -336,15 +349,15 @@ const styles = StyleSheet.create({ borderRadius: borderRadius.lg, padding: spacing.lg, marginBottom: spacing.md, - shadowColor: "#000", + shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 8, elevation: 2, }, reviewHeader: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', marginBottom: spacing.md, }, avatarContainer: { @@ -352,14 +365,14 @@ const styles = StyleSheet.create({ height: 40, borderRadius: 20, backgroundColor: colors.primary, - justifyContent: "center", - alignItems: "center", + justifyContent: 'center', + alignItems: 'center', marginRight: spacing.sm, }, avatarText: { ...typography.body, color: colors.text.onPrimary, - fontWeight: "700", + fontWeight: '700', fontSize: 18, }, reviewHeaderText: { @@ -368,7 +381,7 @@ const styles = StyleSheet.create({ reviewerName: { ...typography.body, color: colors.text.primary, - fontWeight: "600", + fontWeight: '600', }, reviewerEmail: { ...typography.bodySmall, @@ -388,49 +401,49 @@ const styles = StyleSheet.create({ noComment: { ...typography.body, color: colors.text.secondary, - fontStyle: "italic", + fontStyle: 'italic', lineHeight: 22, marginBottom: spacing.sm, }, reviewRatingContainer: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', gap: spacing.xs, marginBottom: spacing.sm, }, reviewRatingText: { ...typography.bodySmall, color: colors.text.primary, - fontWeight: "600", + fontWeight: '600', marginLeft: spacing.xs, }, starsRow: { - flexDirection: "row", + flexDirection: 'row', gap: 2, }, starContainer: { - position: "relative", + position: 'relative', width: 16, height: 16, }, starEmpty: { fontSize: 16, color: colors.border.light, - position: "absolute", + position: 'absolute', }, starFillContainer: { - overflow: "hidden", - position: "absolute", + overflow: 'hidden', + position: 'absolute', height: 16, }, starFilled: { fontSize: 16, - color: "#FFB800", + color: '#FFB800', }, noPhotos: { ...typography.bodySmall, color: colors.text.secondary, - fontStyle: "italic", + fontStyle: 'italic', marginTop: spacing.xs, }, imageScroll: { @@ -448,4 +461,4 @@ const styles = StyleSheet.create({ marginRight: spacing.sm, backgroundColor: colors.border.light, }, -}); \ No newline at end of file +}); diff --git a/mobile/app/(admin)/reviews/_layout.tsx b/mobile/app/(admin)/reviews/_layout.tsx index ff0f171..8ca6017 100644 --- a/mobile/app/(admin)/reviews/_layout.tsx +++ b/mobile/app/(admin)/reviews/_layout.tsx @@ -1,4 +1,4 @@ -import { Stack } from "expo-router"; +import { Stack } from 'expo-router'; export default function ReviewsLayout() { return ; diff --git a/mobile/app/(admin)/settings/page.tsx b/mobile/app/(admin)/settings/page.tsx index 2ad7858..4a8470f 100644 --- a/mobile/app/(admin)/settings/page.tsx +++ b/mobile/app/(admin)/settings/page.tsx @@ -8,4 +8,4 @@ export default function AdminSettingsPage() { rbac?.role === 'admin' ? 'admin' : 'staff'; return ; -} \ No newline at end of file +} diff --git a/mobile/app/(auth)/_layout.tsx b/mobile/app/(auth)/_layout.tsx index 80d3fe6..6185b63 100644 --- a/mobile/app/(auth)/_layout.tsx +++ b/mobile/app/(auth)/_layout.tsx @@ -10,7 +10,14 @@ export default function AuthLayout() { if (!isLoaded) { return ( - + ); @@ -23,14 +30,14 @@ export default function AuthLayout() { return ( - {/* verify.tsx is no longer needed - merged into sign-up */} ); -} \ No newline at end of file +} diff --git a/mobile/app/(auth)/sign-in.tsx b/mobile/app/(auth)/sign-in.tsx index 943015e..a29a60e 100644 --- a/mobile/app/(auth)/sign-in.tsx +++ b/mobile/app/(auth)/sign-in.tsx @@ -93,7 +93,7 @@ export default function SignInScreen() { style={styles.container} > {/* Back Button */} - router.push('/welcome')} > @@ -106,18 +106,18 @@ export default function SignInScreen() { @@ -126,9 +126,9 @@ export default function SignInScreen() { )} - + - + Don't have an account? Sign up @@ -139,7 +139,7 @@ export default function SignInScreen() { - + ); @@ -204,4 +204,4 @@ const styles = StyleSheet.create({ color: colors.primary, fontWeight: '600', }, -}); \ No newline at end of file +}); diff --git a/mobile/app/(auth)/sign-up.tsx b/mobile/app/(auth)/sign-up.tsx index 8503b45..c6c60ad 100644 --- a/mobile/app/(auth)/sign-up.tsx +++ b/mobile/app/(auth)/sign-up.tsx @@ -137,16 +137,16 @@ export default function SignUpScreen() { const handleResendCode = async () => { if (!isLoaded) return; - + try { await signUp.prepareVerification({ strategy: 'email_code' }); clearErrors('code'); // Show success feedback - setError('code', { + setError('code', { message: '✓ New code sent! Check your email.', - type: 'success' as any + type: 'success' as any, }); - + // Clear the success message after 3 seconds setTimeout(() => { clearErrors('code'); @@ -163,7 +163,7 @@ export default function SignUpScreen() { style={styles.container} > {/* Back Button */} - router.push('/welcome')} > @@ -184,18 +184,18 @@ export default function SignUpScreen() { <> @@ -217,14 +217,14 @@ export default function SignUpScreen() { > - + {/* Resend Code Link */} Didn't receive a code?{' '} @@ -247,7 +247,7 @@ export default function SignUpScreen() { {!pendingVerification && ( <> - + Already have an account? Sign in @@ -258,16 +258,13 @@ export default function SignUpScreen() { - + )} {pendingVerification && ( - + Back to sign in )} @@ -344,4 +341,4 @@ const styles = StyleSheet.create({ color: colors.primary, fontWeight: '600', }, -}); \ No newline at end of file +}); diff --git a/mobile/app/(onboarding)/_layout.tsx b/mobile/app/(onboarding)/_layout.tsx index bf5fd5d..17ddc1c 100644 --- a/mobile/app/(onboarding)/_layout.tsx +++ b/mobile/app/(onboarding)/_layout.tsx @@ -10,4 +10,4 @@ export default function OnboardingLayout() { }} /> ); -} \ No newline at end of file +} diff --git a/mobile/app/(onboarding)/onboarding.tsx b/mobile/app/(onboarding)/onboarding.tsx index 5b6b960..12e5dec 100644 --- a/mobile/app/(onboarding)/onboarding.tsx +++ b/mobile/app/(onboarding)/onboarding.tsx @@ -1,6 +1,14 @@ // app/(onboarding)/onboarding.tsx import React, { useState, useRef } from 'react'; -import { View, Text, StyleSheet, Pressable, ScrollView, TextInput, Dimensions } from 'react-native'; +import { + View, + Text, + StyleSheet, + Pressable, + ScrollView, + TextInput, + Dimensions, +} from 'react-native'; import PagerView from 'react-native-pager-view'; import { router } from 'expo-router'; import { useAuth, useUser } from '@clerk/clerk-expo'; @@ -23,7 +31,9 @@ export default function OnboardingScreen() { const [name, setName] = useState(user?.fullName || ''); const [selectedLocations, setSelectedLocations] = useState([]); const [agreedToTerms, setAgreedToTerms] = useState(false); - const [selectedRole, setSelectedRole] = useState<'student' | 'staff'>('student'); + const [selectedRole, setSelectedRole] = useState<'student' | 'staff'>( + 'student', + ); const [isSubmitting, setIsSubmitting] = useState(false); const styles = createStyles(colors); @@ -51,10 +61,10 @@ export default function OnboardingScreen() { }; const toggleLocation = (location: string) => { - setSelectedLocations(prev => + setSelectedLocations((prev) => prev.includes(location) - ? prev.filter(l => l !== location) - : [...prev, location] + ? prev.filter((l) => l !== location) + : [...prev, location], ); }; @@ -72,7 +82,7 @@ export default function OnboardingScreen() { locPref: selectedLocations, agreedToTerms: true, }, - { merge: true } + { merge: true }, ); const userRole = user.publicMetadata?.role as string | undefined; @@ -106,9 +116,9 @@ export default function OnboardingScreen() { const canProceedFromPage = (page: number) => { if (page === 0) return name.trim().length > 0; // Name - if (page === 1) return true; // Role (default is student) - if (page === 2) return true; // Campus prefs - if (page === 3) return agreedToTerms; // Terms + if (page === 1) return true; // Role (default is student) + if (page === 2) return true; // Campus prefs + if (page === 3) return agreedToTerms; // Terms return false; }; @@ -132,7 +142,7 @@ export default function OnboardingScreen() { 👋 Welcome to BU Catering Let's get to know you - + What's your name? Browse and claim leftover catering events on campus. @@ -223,28 +234,36 @@ export default function OnboardingScreen() { 📍 Campus Preferences - Select your preferred campus sections to get relevant event notifications - + + Select your preferred campus sections to get relevant event + notifications + + {CAMPUS_SECTIONS.map((section) => ( toggleLocation(section)} > - + {section} ))} - You can change this later in settings + + You can change this later in settings + @@ -253,21 +272,29 @@ export default function OnboardingScreen() { 📜 Terms & Conditions - - - - {TERMS_AND_CONDITIONS} - + + + {TERMS_AND_CONDITIONS} setAgreedToTerms(!agreedToTerms)} > - + {agreedToTerms && } - I agree to the Terms & Conditions + + I agree to the Terms & Conditions + @@ -277,7 +304,9 @@ export default function OnboardingScreen() { {currentPage > 0 && ( - Back + + Back + )} @@ -285,10 +314,7 @@ export default function OnboardingScreen() { {[0, 1, 2, 3].map((index) => ( ))} @@ -300,7 +326,7 @@ export default function OnboardingScreen() { roleNoteSelected: { color: colors.primary, }, - }); \ No newline at end of file + }); diff --git a/mobile/app/(shared)/_layout.tsx b/mobile/app/(shared)/_layout.tsx index dddc2ef..c898ca2 100644 --- a/mobile/app/(shared)/_layout.tsx +++ b/mobile/app/(shared)/_layout.tsx @@ -3,4 +3,4 @@ import { Stack } from 'expo-router'; export default function SharedLayout() { return ; -} \ No newline at end of file +} diff --git a/mobile/app/(shared)/settings/_layout.tsx b/mobile/app/(shared)/settings/_layout.tsx index 1e56019..36d2096 100644 --- a/mobile/app/(shared)/settings/_layout.tsx +++ b/mobile/app/(shared)/settings/_layout.tsx @@ -4,7 +4,7 @@ import { useTheme } from '../../../src/lib/ThemeProvider'; export default function SettingsLayout() { const { colors } = useTheme(); - + return ( ); -} \ No newline at end of file +} diff --git a/mobile/app/(shared)/settings/edit-location.tsx b/mobile/app/(shared)/settings/edit-location.tsx index 93ba1ba..44f6888 100644 --- a/mobile/app/(shared)/settings/edit-location.tsx +++ b/mobile/app/(shared)/settings/edit-location.tsx @@ -1,6 +1,13 @@ // app/(shared)/settings/edit-location.tsx import React, { useState, useEffect, useCallback } from 'react'; -import { View, Text, StyleSheet, Pressable, Alert, ScrollView } from 'react-native'; +import { + View, + Text, + StyleSheet, + Pressable, + Alert, + ScrollView, +} from 'react-native'; import { Stack, router } from 'expo-router'; import { useUser } from '@clerk/clerk-expo'; import { doc, getDoc, updateDoc } from 'firebase/firestore'; @@ -24,11 +31,11 @@ export default function EditLocationScreen() { const loadCurrentLocations = async () => { if (!user) return; - + try { const userRef = doc(firestore, 'Users', user.id); const userSnap = await getDoc(userRef); - + if (userSnap.exists()) { const userData = userSnap.data(); setSelectedLocations(userData.locPref || []); @@ -41,10 +48,10 @@ export default function EditLocationScreen() { }; const toggleLocation = (location: string) => { - setSelectedLocations(prev => + setSelectedLocations((prev) => prev.includes(location) - ? prev.filter(l => l !== location) - : [...prev, location] + ? prev.filter((l) => l !== location) + : [...prev, location], ); }; @@ -57,7 +64,7 @@ export default function EditLocationScreen() { await updateDoc(userRef, { locPref: selectedLocations, }); - + const role = user.publicMetadata?.role as string | undefined; if (role === 'admin' || role === 'staff') { router.replace('/(admin)/settings/page'); @@ -82,47 +89,56 @@ export default function EditLocationScreen() { return ( <> - ( - - + + ), }} /> - - Select your preferred campus sections to get relevant event notifications + Select your preferred campus sections to get relevant event + notifications - + {CAMPUS_SECTIONS.map((section) => ( toggleLocation(section)} > - + {section} @@ -134,7 +150,9 @@ export default function EditLocationScreen() { onPress={handleSave} disabled={isSaving} > - + {isSaving ? 'Saving...' : 'Save'} @@ -179,4 +197,4 @@ const styles = StyleSheet.create({ ...typography.body, fontWeight: '600', }, -}); \ No newline at end of file +}); diff --git a/mobile/app/(shared)/settings/edit-name.tsx b/mobile/app/(shared)/settings/edit-name.tsx index f02d61e..96dba04 100644 --- a/mobile/app/(shared)/settings/edit-name.tsx +++ b/mobile/app/(shared)/settings/edit-name.tsx @@ -1,6 +1,15 @@ // app/(shared)/settings/edit-name.tsx import React, { useState, useEffect, useCallback } from 'react'; -import { View, Text, StyleSheet, TextInput, Pressable, Alert, KeyboardAvoidingView, Platform } from 'react-native'; +import { + View, + Text, + StyleSheet, + TextInput, + Pressable, + Alert, + KeyboardAvoidingView, + Platform, +} from 'react-native'; import { Stack, router } from 'expo-router'; import { useUser } from '@clerk/clerk-expo'; import { doc, getDoc, updateDoc } from 'firebase/firestore'; @@ -22,11 +31,11 @@ export default function EditNameScreen() { const loadCurrentName = async () => { if (!user) return; - + try { const userRef = doc(firestore, 'Users', user.id); const userSnap = await getDoc(userRef); - + if (userSnap.exists()) { const userData = userSnap.data(); setName(userData.name || ''); @@ -50,7 +59,7 @@ export default function EditNameScreen() { await updateDoc(userRef, { name: name.trim(), }); - + const role = user.publicMetadata?.role as string | undefined; if (role === 'admin' || role === 'staff') { router.replace('/(admin)/settings/page'); @@ -75,21 +84,22 @@ export default function EditNameScreen() { return ( <> - ( - - + + ), }} /> - @@ -97,15 +107,20 @@ export default function EditNameScreen() { This is the name others will see when referring to you - + - Name + + Name + - + {isSaving ? 'Saving...' : 'Save'} @@ -163,4 +180,4 @@ const styles = StyleSheet.create({ ...typography.body, fontWeight: '600', }, -}); \ No newline at end of file +}); diff --git a/mobile/app/(student)/_layout.tsx b/mobile/app/(student)/_layout.tsx index ee396a2..4d182aa 100644 --- a/mobile/app/(student)/_layout.tsx +++ b/mobile/app/(student)/_layout.tsx @@ -13,14 +13,21 @@ export default function StudentLayout() { if (!isLoaded) { return ( - + ); } if (!isSignedIn) { - return ; + return ; } return ( @@ -49,10 +56,7 @@ export default function StudentLayout() { }} /> - + - + ); } diff --git a/mobile/app/(student)/feedback.tsx b/mobile/app/(student)/feedback.tsx index 8b6da45..d4093d0 100644 --- a/mobile/app/(student)/feedback.tsx +++ b/mobile/app/(student)/feedback.tsx @@ -1,351 +1,376 @@ import { - View, - Text, - TextInput, - Pressable, - StyleSheet, - ScrollView, - Image, - Alert, - Switch, -} from "react-native"; -import { useRouter, useLocalSearchParams } from "expo-router"; -import { addDoc, collection, updateDoc, doc, arrayUnion, serverTimestamp, getDoc, setDoc } from "firebase/firestore"; -import { firestore, storage } from "../../src/lib/firebase/config"; -import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; -import * as ImagePicker from "expo-image-picker"; -import { useUser } from "@clerk/clerk-expo"; -import { colors, typography, spacing, borderRadius } from "../../src/lib/theme"; -import { useEffect, useState } from "react"; -import BUlogo from "../../assets/boston-university-logo.png"; -import { Ionicons } from "@expo/vector-icons"; + View, + Text, + TextInput, + Pressable, + StyleSheet, + ScrollView, + Image, + Alert, + Switch, +} from 'react-native'; +import { useRouter, useLocalSearchParams } from 'expo-router'; +import { + addDoc, + collection, + updateDoc, + doc, + arrayUnion, + serverTimestamp, + getDoc, + setDoc, +} from 'firebase/firestore'; +import { firestore, storage } from '../../src/lib/firebase/config'; +import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; +import * as ImagePicker from 'expo-image-picker'; +import { useUser } from '@clerk/clerk-expo'; +import { colors, typography, spacing, borderRadius } from '../../src/lib/theme'; +import { useEffect, useState } from 'react'; +import BUlogo from '../../assets/boston-university-logo.png'; +import { Ionicons } from '@expo/vector-icons'; export default function FeedbackPage() { - const router = useRouter(); - const { eventId } = useLocalSearchParams<{ eventId: string }>(); - const { user } = useUser(); - - const [rating, setRating] = useState(0); - const [comment, setComment] = useState(""); - const [uploadedImage, setUploadedImage] = useState(null); - const [uploading, setUploading] = useState(false); - const [eventName, setEventName] = useState("Loading..."); - const [shareContact, setShareContact] = useState(false); - - useEffect(() => { - const fetchEventName = async () => { - if (!eventId) return; - try { - const docRef = doc(firestore, "Events", eventId); - const docSnap = await getDoc(docRef); - if (docSnap.exists()) { - const data = docSnap.data(); - setEventName(data.name || "Unnamed Event"); - } else { - setEventName("Unknown Event"); - } - } catch (error) { - console.error("Error fetching event name:", error); - setEventName("Error loading event"); - } - }; + const router = useRouter(); + const { eventId } = useLocalSearchParams<{ eventId: string }>(); + const { user } = useUser(); - fetchEventName(); - }, [eventId]); + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(''); + const [uploadedImage, setUploadedImage] = useState(null); + const [uploading, setUploading] = useState(false); + const [eventName, setEventName] = useState('Loading...'); + const [shareContact, setShareContact] = useState(false); - // ⭐ Handle rating stars - const renderStars = () => { - const stars = []; - for (let i = 1; i <= 5; i++) { - stars.push( - setRating(i)}> - - - ); + useEffect(() => { + const fetchEventName = async () => { + if (!eventId) return; + try { + const docRef = doc(firestore, 'Events', eventId); + const docSnap = await getDoc(docRef); + if (docSnap.exists()) { + const data = docSnap.data(); + setEventName(data.name || 'Unnamed Event'); + } else { + setEventName('Unknown Event'); } - return stars; + } catch (error) { + console.error('Error fetching event name:', error); + setEventName('Error loading event'); + } }; - // 🖼️ Upload image - const handleUploadPhoto = async () => { - try { - const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); - if (permission.status !== "granted") { - Alert.alert("Permission required", "We need photo access to upload an image."); - return; - } + fetchEventName(); + }, [eventId]); - const result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.Images, - quality: 0.8, - }); + // ⭐ Handle rating stars + const renderStars = () => { + const stars = []; + for (let i = 1; i <= 5; i++) { + stars.push( + setRating(i)}> + + ★ + + , + ); + } + return stars; + }; - if (result.canceled) return; + // 🖼️ Upload image + const handleUploadPhoto = async () => { + try { + const permission = + await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (permission.status !== 'granted') { + Alert.alert( + 'Permission required', + 'We need photo access to upload an image.', + ); + return; + } - const asset = result.assets[0]; - setUploading(true); + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + quality: 0.8, + }); - const response = await fetch(asset.uri); - const blob = await response.blob(); - const fileName = `feedback_${eventId}_${Date.now()}.jpg`; - const storageRef = ref(storage, `feedback/${fileName}`); - await uploadBytes(storageRef, blob); - const downloadURL = await getDownloadURL(storageRef); + if (result.canceled) return; - setUploadedImage(downloadURL); - setUploading(false); - } catch (err) { - console.error(err); - Alert.alert("Error", "Failed to upload image."); - setUploading(false); - } - }; + const asset = result.assets[0]; + setUploading(true); - // 📨 Submit feedback - const handleSubmit = async () => { - if (rating === 0) { - Alert.alert("Rating Required", "Please provide a rating for the food."); - return; - } + const response = await fetch(asset.uri); + const blob = await response.blob(); + const fileName = `feedback_${eventId}_${Date.now()}.jpg`; + const storageRef = ref(storage, `feedback/${fileName}`); + await uploadBytes(storageRef, blob); + const downloadURL = await getDownloadURL(storageRef); - try { - const feedbackRef = collection(firestore, "Reviews", eventId, "Reviews"); - const docRef = await addDoc(feedbackRef, { - rating, - comment: comment.trim(), - images: uploadedImage ? [uploadedImage] : [], - date: serverTimestamp(), - name: user?.fullName || "", - email: user?.primaryEmailAddress?.emailAddress || "", - shareContact, - }); - await updateDoc(docRef, { id: docRef.id }); + setUploadedImage(downloadURL); + setUploading(false); + } catch (err) { + console.error(err); + Alert.alert('Error', 'Failed to upload image.'); + setUploading(false); + } + }; - await setDoc( - doc(firestore, "Users", user?.id || ""), - { reviews: arrayUnion(eventId) }, - { merge: true } - ); + // 📨 Submit feedback + const handleSubmit = async () => { + if (rating === 0) { + Alert.alert('Rating Required', 'Please provide a rating for the food.'); + return; + } - await updateDoc(doc(firestore, "Events", eventId), { - reviewedBy: arrayUnion(user?.id), - }); + try { + const feedbackRef = collection(firestore, 'Reviews', eventId, 'Reviews'); + const docRef = await addDoc(feedbackRef, { + rating, + comment: comment.trim(), + images: uploadedImage ? [uploadedImage] : [], + date: serverTimestamp(), + name: user?.fullName || '', + email: user?.primaryEmailAddress?.emailAddress || '', + shareContact, + }); + await updateDoc(docRef, { id: docRef.id }); - Alert.alert("Thank you!", "Your feedback has been submitted."); - router.back(); - } catch (err) { - console.error(err); - Alert.alert("Error", "Failed to submit feedback."); - } - }; + await setDoc( + doc(firestore, 'Users', user?.id || ''), + { reviews: arrayUnion(eventId) }, + { merge: true }, + ); - return ( - - {/* Back Button*/} - router.back()}> - - + await updateDoc(doc(firestore, 'Events', eventId), { + reviewedBy: arrayUnion(user?.id), + }); - {/* BU Logo */} - - - + Alert.alert('Thank you!', 'Your feedback has been submitted.'); + router.back(); + } catch (err) { + console.error(err); + Alert.alert('Error', 'Failed to submit feedback.'); + } + }; - {/* Dropdown*/} - - {eventName} - + return ( + + {/* Back Button*/} + router.back()}> + + - {/* Upload Photo */} - Upload Photo (Optional) - - {uploadedImage ? ( - - ) : ( - <> - - - {uploading ? "Uploading..." : "Upload Photo"} - - - )} - + {/* BU Logo */} + + + - {/* Rating */} - How would you rate the food? * - {renderStars()} + {/* Dropdown*/} + + {eventName} + - {/* Feedback */} - Feedback (Optional) - Upload Photo (Optional) + + {uploadedImage ? ( + + ) : ( + <> + + + {uploading ? 'Uploading...' : 'Upload Photo'} + + + )} + + + {/* Rating */} + How would you rate the food? * + {renderStars()} + + {/* Feedback */} + Feedback (Optional) + - {/* Share Contact Toggle */} - - - Share my contact info with organizers - - Let event hosts know who you are - - - - + {/* Share Contact Toggle */} + + + + Share my contact info with organizers + + + Let event hosts know who you are + + + + - {/* Submit Button */} - - Send - - - ); + {/* Submit Button */} + + Send + + + ); } const styles = StyleSheet.create({ - container: { - backgroundColor: colors.background, - flex: 1, - }, - backIcon: { - position: "absolute", - top: 60, - left: 20, - zIndex: 10, - }, - backIconText: { - fontSize: 28, - color: colors.secondary, - fontWeight: "400", - }, - logoContainer: { - alignItems: "center", - marginBottom: spacing.xl, - marginTop: spacing.xxl + 20, - }, - logoImage: { - width: 220, - height: 60, - resizeMode: "contain", - }, - dropdown: { - borderWidth: 1, - borderColor: colors.primary, - borderRadius: borderRadius.sm, - padding: spacing.md, - marginBottom: spacing.lg, - }, - dropdownText: { - color: colors.secondary, - ...typography.body, - }, - sectionLabel: { - ...typography.bodySmall, - color: colors.text.secondary, - marginBottom: spacing.xs, - }, - uploadBox: { - borderWidth: 1.5, - borderColor: colors.primary, - borderRadius: borderRadius.sm, - height: 140, - justifyContent: "center", - alignItems: "center", - marginBottom: spacing.lg, - }, - uploadText: { - ...typography.body, - color: colors.primary, - fontWeight: "600", - }, - uploadPreview: { - width: "100%", - height: "100%", - borderRadius: borderRadius.sm, - resizeMode: "cover", - }, - ratingLabel: { - ...typography.body, - color: colors.text.primary, - fontWeight: "600", - marginBottom: spacing.sm, - }, - starContainer: { - flexDirection: "row", - marginBottom: spacing.lg, - }, - star: { - fontSize: 32, - color: colors.border.default, - marginRight: spacing.sm, - }, - starSelected: { - color: colors.primary, - }, - feedbackLabel: { - ...typography.body, - color: colors.text.primary, - fontWeight: "600", - marginBottom: spacing.xs, - }, - textArea: { - borderWidth: 1, - borderColor: colors.primary, - borderRadius: borderRadius.sm, - backgroundColor: colors.surface, - padding: spacing.md, - minHeight: 100, - marginBottom: spacing.lg, - ...typography.body, - color: colors.text.primary, - }, - toggleContainer: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - backgroundColor: colors.surface, - padding: spacing.md, - borderRadius: borderRadius.sm, - marginBottom: spacing.xl, - borderWidth: 1, - borderColor: colors.border.light, - }, - toggleTextContainer: { - flex: 1, - marginRight: spacing.md, - }, - toggleLabel: { - ...typography.body, - color: colors.text.primary, - fontWeight: "600", - marginBottom: spacing.xs / 2, - }, - toggleSubtext: { - ...typography.caption, - color: colors.text.secondary, - }, - sendButton: { - backgroundColor: colors.primary, - borderRadius: 20, - alignItems: "center", - justifyContent: "center", - paddingVertical: spacing.md, - width: 100, - alignSelf: "center", - marginBottom: spacing.xl, - }, - sendText: { - color: colors.text.onPrimary, - ...typography.body, - fontWeight: "600", - }, -}); \ No newline at end of file + container: { + backgroundColor: colors.background, + flex: 1, + }, + backIcon: { + position: 'absolute', + top: 60, + left: 20, + zIndex: 10, + }, + backIconText: { + fontSize: 28, + color: colors.secondary, + fontWeight: '400', + }, + logoContainer: { + alignItems: 'center', + marginBottom: spacing.xl, + marginTop: spacing.xxl + 20, + }, + logoImage: { + width: 220, + height: 60, + resizeMode: 'contain', + }, + dropdown: { + borderWidth: 1, + borderColor: colors.primary, + borderRadius: borderRadius.sm, + padding: spacing.md, + marginBottom: spacing.lg, + }, + dropdownText: { + color: colors.secondary, + ...typography.body, + }, + sectionLabel: { + ...typography.bodySmall, + color: colors.text.secondary, + marginBottom: spacing.xs, + }, + uploadBox: { + borderWidth: 1.5, + borderColor: colors.primary, + borderRadius: borderRadius.sm, + height: 140, + justifyContent: 'center', + alignItems: 'center', + marginBottom: spacing.lg, + }, + uploadText: { + ...typography.body, + color: colors.primary, + fontWeight: '600', + }, + uploadPreview: { + width: '100%', + height: '100%', + borderRadius: borderRadius.sm, + resizeMode: 'cover', + }, + ratingLabel: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + marginBottom: spacing.sm, + }, + starContainer: { + flexDirection: 'row', + marginBottom: spacing.lg, + }, + star: { + fontSize: 32, + color: colors.border.default, + marginRight: spacing.sm, + }, + starSelected: { + color: colors.primary, + }, + feedbackLabel: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + marginBottom: spacing.xs, + }, + textArea: { + borderWidth: 1, + borderColor: colors.primary, + borderRadius: borderRadius.sm, + backgroundColor: colors.surface, + padding: spacing.md, + minHeight: 100, + marginBottom: spacing.lg, + ...typography.body, + color: colors.text.primary, + }, + toggleContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.surface, + padding: spacing.md, + borderRadius: borderRadius.sm, + marginBottom: spacing.xl, + borderWidth: 1, + borderColor: colors.border.light, + }, + toggleTextContainer: { + flex: 1, + marginRight: spacing.md, + }, + toggleLabel: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + marginBottom: spacing.xs / 2, + }, + toggleSubtext: { + ...typography.caption, + color: colors.text.secondary, + }, + sendButton: { + backgroundColor: colors.primary, + borderRadius: 20, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.md, + width: 100, + alignSelf: 'center', + marginBottom: spacing.xl, + }, + sendText: { + color: colors.text.onPrimary, + ...typography.body, + fontWeight: '600', + }, +}); diff --git a/mobile/app/(student)/index.tsx b/mobile/app/(student)/index.tsx index 05aafbc..4723a8a 100644 --- a/mobile/app/(student)/index.tsx +++ b/mobile/app/(student)/index.tsx @@ -7,7 +7,9 @@ import { useOpenEvents } from '../../src/hooks/useEvents'; import { EventCard } from '../../src/components/EventCard'; import React from 'react'; import SortDropdown, { SortOption } from '../../src/components/SortDropdown'; -import LocationFilterDropdown, { LocationFilterOption } from '../../src/components/LocationFilterDropdown'; +import LocationFilterDropdown, { + LocationFilterOption, +} from '../../src/components/LocationFilterDropdown'; import { getExpiryMs } from '../../src/lib/time'; import { PRESET_LOCATIONS } from '../../src/lib/constants'; @@ -16,12 +18,14 @@ export default function StudentHomeScreen() { const { colors } = useTheme(); const { events, loading, refresh } = useOpenEvents(); const [sort, setSort] = React.useState('expiry-asc'); - const [selectedLocations, setSelectedLocations] = React.useState>(new Set()); + const [selectedLocations, setSelectedLocations] = React.useState< + Set + >(new Set()); // Get preset location names for matching const presetLocationNames = React.useMemo( () => new Set(PRESET_LOCATIONS.map((p) => p.name)), - [] + [], ); const filteredAndSortedEvents = React.useMemo(() => { @@ -53,71 +57,79 @@ export default function StudentHomeScreen() { // Then sort by expiry const withExpiry = filtered.map((e) => ({ e, - expiry: getExpiryMs({ foodAvailable: e.foodAvailable, duration: e.duration }), + expiry: getExpiryMs({ + foodAvailable: e.foodAvailable, + duration: e.duration, + }), })); const expiryFiltered = withExpiry.filter( - (x) => typeof x.expiry === 'number' && x.expiry !== null + (x) => typeof x.expiry === 'number' && x.expiry !== null, ); expiryFiltered.sort((a, b) => { - if (sort === 'expiry-asc') return (a.expiry as number) - (b.expiry as number); + if (sort === 'expiry-asc') + return (a.expiry as number) - (b.expiry as number); return (b.expiry as number) - (a.expiry as number); }); const missing = withExpiry.filter((x) => x.expiry === null); return [...expiryFiltered.map((x) => x.e), ...missing.map((x) => x.e)]; }, [events, sort, selectedLocations, presetLocationNames]); - const styles = React.useMemo(() => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.background, - }, - header: { - padding: spacing.lg, - paddingTop: spacing.xxl + 20, - backgroundColor: colors.surface, - borderBottomWidth: 1, - borderBottomColor: colors.border.light, - }, - greeting: { - ...typography.h4, - color: colors.text.primary, - }, - subtitle: { - ...typography.bodySmall, - color: colors.text.secondary, - marginTop: spacing.xs, - }, - sortRow: { - paddingHorizontal: spacing.lg, - paddingTop: spacing.sm, - paddingBottom: spacing.xs, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', - }, - filterButton: { - marginRight: spacing.sm, - }, - listContent: { - padding: spacing.lg, - }, - emptyState: { - alignItems: 'center', - justifyContent: 'center', - paddingVertical: spacing.xxl * 2, - }, - emptyText: { - ...typography.h5, - color: colors.text.secondary, - textAlign: 'center', - }, - emptySubtext: { - ...typography.body, - color: colors.text.secondary, - textAlign: 'center', - marginTop: spacing.sm, - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + padding: spacing.lg, + paddingTop: spacing.xxl + 20, + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderBottomColor: colors.border.light, + }, + greeting: { + ...typography.h4, + color: colors.text.primary, + }, + subtitle: { + ...typography.bodySmall, + color: colors.text.secondary, + marginTop: spacing.xs, + }, + sortRow: { + paddingHorizontal: spacing.lg, + paddingTop: spacing.sm, + paddingBottom: spacing.xs, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + }, + filterButton: { + marginRight: spacing.sm, + }, + listContent: { + padding: spacing.lg, + }, + emptyState: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.xxl * 2, + }, + emptyText: { + ...typography.h5, + color: colors.text.secondary, + textAlign: 'center', + }, + emptySubtext: { + ...typography.body, + color: colors.text.secondary, + textAlign: 'center', + marginTop: spacing.sm, + }, + }), + [colors], + ); return ( @@ -160,12 +172,10 @@ export default function StudentHomeScreen() { {loading ? 'Loading events...' : 'No open events right now'} - - Pull down to refresh - + Pull down to refresh } /> ); -} \ No newline at end of file +} diff --git a/mobile/app/(student)/settings/faq.tsx b/mobile/app/(student)/settings/faq.tsx index 2966800..531532d 100644 --- a/mobile/app/(student)/settings/faq.tsx +++ b/mobile/app/(student)/settings/faq.tsx @@ -106,7 +106,13 @@ export default function StudentFAQ() { paddingVertical: spacing.md, paddingHorizontal: spacing.lg, }, - questionText: { ...typography.body, color: colors.text.primary, fontWeight: '600', flex: 1, paddingRight: spacing.md }, + questionText: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + flex: 1, + paddingRight: spacing.md, + }, chevron: { marginLeft: spacing.sm }, answer: { paddingHorizontal: spacing.lg, @@ -114,10 +120,11 @@ export default function StudentFAQ() { }, answerText: { ...typography.body, color: colors.text.secondary }, }), - [colors] + [colors], ); - const toggle = (id: string) => setExpandedId(prev => (prev === id ? null : id)); + const toggle = (id: string) => + setExpandedId((prev) => (prev === id ? null : id)); return ( @@ -134,7 +141,7 @@ export default function StudentFAQ() { - {STUDENT_FAQS.map(item => { + {STUDENT_FAQS.map((item) => { const open = expandedId === item.id; return ( diff --git a/mobile/app/(student)/settings/page.tsx b/mobile/app/(student)/settings/page.tsx index ed4ad84..f0d38e9 100644 --- a/mobile/app/(student)/settings/page.tsx +++ b/mobile/app/(student)/settings/page.tsx @@ -3,4 +3,4 @@ import SettingsScreen from '../../../src/components/SettingsScreen'; export default function StudentSettingsPage() { return ; -} \ No newline at end of file +} diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index 95aaf3e..b5dcab1 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -10,7 +10,10 @@ const CLERK_PUBLISHABLE_KEY = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY; export default function RootLayout() { return ( - + diff --git a/mobile/app/welcome.tsx b/mobile/app/welcome.tsx index 233b0f2..32dee08 100644 --- a/mobile/app/welcome.tsx +++ b/mobile/app/welcome.tsx @@ -2,7 +2,14 @@ import { useEffect, useState } from 'react'; import { firestore } from '../src/lib/firebase/config'; import { doc, setDoc, getDoc } from 'firebase/firestore'; -import { Text, StyleSheet, View, ActivityIndicator, Pressable, Image } from 'react-native'; +import { + Text, + StyleSheet, + View, + ActivityIndicator, + Pressable, + Image, +} from 'react-native'; import { Redirect, router } from 'expo-router'; import { useAuth, useUser } from '@clerk/clerk-expo'; import { colors, typography, spacing } from '../src/lib/theme'; @@ -50,7 +57,7 @@ export default function WelcomeScreen() { } const userData = userSnap.data(); - console.log(userData.agreedToTerms) + console.log(userData.agreedToTerms); if (userData.agreedToTerms !== true) { console.log('📋 User needs to complete onboarding'); setShouldRedirect({ path: '/(onboarding)/onboarding', ready: true }); @@ -87,7 +94,10 @@ export default function WelcomeScreen() { // Default: student experience setShouldRedirect({ path: '/(student)', ready: true }); } catch (err) { - console.error('Failed to load RBAC info, falling back to student route:', err); + console.error( + 'Failed to load RBAC info, falling back to student route:', + err, + ); setShouldRedirect({ path: '/(student)', ready: true }); } } catch (error) { @@ -129,7 +139,9 @@ export default function WelcomeScreen() { /> Welcome to FreeBites - Connect with your community through food + + Connect with your community through food + router.push('/sign-up')} > - Sign Up + + Sign Up + @@ -211,4 +225,4 @@ const styles = StyleSheet.create({ signupButtonText: { color: colors.primary, }, -}); \ No newline at end of file +}); diff --git a/mobile/src/components/CoreImpactMetrics.tsx b/mobile/src/components/CoreImpactMetrics.tsx index d389be3..e9e76b8 100644 --- a/mobile/src/components/CoreImpactMetrics.tsx +++ b/mobile/src/components/CoreImpactMetrics.tsx @@ -1,5 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, ActivityIndicator, StyleSheet, FlatList } from 'react-native'; +import { + View, + Text, + ActivityIndicator, + StyleSheet, + FlatList, +} from 'react-native'; import { collection, query, where, onSnapshot } from 'firebase/firestore'; import { firestore } from '../lib/firebase/config'; import { useTheme } from '../lib/ThemeProvider'; @@ -10,7 +16,9 @@ export default function CoreImpactMetrics() { const [loading, setLoading] = useState(true); const [totalTrays, setTotalTrays] = useState(0); const [eventCount, setEventCount] = useState(0); - const [foodTypeCounts, setFoodTypeCounts] = useState>({}); + const [foodTypeCounts, setFoodTypeCounts] = useState>( + {}, + ); useEffect(() => { const eventsRef = collection(firestore, 'Events'); @@ -57,84 +65,91 @@ export default function CoreImpactMetrics() { (error) => { console.error('Error fetching metrics:', error); setLoading(false); - } + }, ); return () => unsubscribe(); }, []); - const avgTrays = eventCount > 0 ? (totalTrays / eventCount).toFixed(1) : '0.0'; + const avgTrays = + eventCount > 0 ? (totalTrays / eventCount).toFixed(1) : '0.0'; const estimatedMeals = totalTrays * 8; - const foodTypeArray = Object.entries(foodTypeCounts).sort((a, b) => b[1] - a[1]); - - const styles = React.useMemo(() => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.background, - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - metricGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - paddingHorizontal: spacing.lg, - marginBottom: spacing.xl, - }, - metricCard: { - width: '48%', - backgroundColor: colors.surface, - borderRadius: borderRadius.lg, - paddingVertical: spacing.xl, - alignItems: 'center', - marginBottom: spacing.lg, - shadowColor: '#000', - shadowOpacity: 0.08, - shadowOffset: { width: 0, height: elevation.md }, - shadowRadius: elevation.lg, - }, - metricTitle: { - ...typography.bodySmall, - color: colors.text.secondary, - marginBottom: spacing.xs, - }, - metricValue: { - ...typography.h3, - color: colors.primary, - fontWeight: 'bold', - }, - section: { - paddingHorizontal: spacing.lg, - }, - sectionTitle: { - ...typography.h5, - color: colors.text.primary, - marginBottom: spacing.md, - }, - foodRow: { - flexDirection: 'row', - justifyContent: 'space-between', - paddingVertical: spacing.sm, - borderBottomWidth: 1, - borderBottomColor: colors.border.light, - }, - foodName: { - ...typography.body, - color: colors.text.primary, - }, - foodCount: { - ...typography.body, - color: colors.text.secondary, - }, - emptyText: { - ...typography.bodySmall, - color: colors.text.secondary, - textAlign: 'center', - }, - }), [colors]); + const foodTypeArray = Object.entries(foodTypeCounts).sort( + (a, b) => b[1] - a[1], + ); + + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + metricGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + paddingHorizontal: spacing.lg, + marginBottom: spacing.xl, + }, + metricCard: { + width: '48%', + backgroundColor: colors.surface, + borderRadius: borderRadius.lg, + paddingVertical: spacing.xl, + alignItems: 'center', + marginBottom: spacing.lg, + shadowColor: '#000', + shadowOpacity: 0.08, + shadowOffset: { width: 0, height: elevation.md }, + shadowRadius: elevation.lg, + }, + metricTitle: { + ...typography.bodySmall, + color: colors.text.secondary, + marginBottom: spacing.xs, + }, + metricValue: { + ...typography.h3, + color: colors.primary, + fontWeight: 'bold', + }, + section: { + paddingHorizontal: spacing.lg, + }, + sectionTitle: { + ...typography.h5, + color: colors.text.primary, + marginBottom: spacing.md, + }, + foodRow: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: spacing.sm, + borderBottomWidth: 1, + borderBottomColor: colors.border.light, + }, + foodName: { + ...typography.body, + color: colors.text.primary, + }, + foodCount: { + ...typography.body, + color: colors.text.secondary, + }, + emptyText: { + ...typography.bodySmall, + color: colors.text.secondary, + textAlign: 'center', + }, + }), + [colors], + ); if (loading) { return ( @@ -148,10 +163,26 @@ export default function CoreImpactMetrics() { {/* Metric Cards */} - - - - + + + + {/* Food Type Distribution */} @@ -177,7 +208,15 @@ export default function CoreImpactMetrics() { } /* ========== Subcomponents ========== */ -function MetricCard({ title, value, styles }: { title: string; value: string | number; styles: any }) { +function MetricCard({ + title, + value, + styles, +}: { + title: string; + value: string | number; + styles: any; +}) { return ( {title} diff --git a/mobile/src/components/CustomButton.tsx b/mobile/src/components/CustomButton.tsx index ec7ce12..1cae7d1 100644 --- a/mobile/src/components/CustomButton.tsx +++ b/mobile/src/components/CustomButton.tsx @@ -25,4 +25,4 @@ const styles = StyleSheet.create({ ...typography.body, fontWeight: '600', }, -}); \ No newline at end of file +}); diff --git a/mobile/src/components/CustomInput.tsx b/mobile/src/components/CustomInput.tsx index 8b53a38..12c8c4d 100644 --- a/mobile/src/components/CustomInput.tsx +++ b/mobile/src/components/CustomInput.tsx @@ -68,4 +68,4 @@ const styles = StyleSheet.create({ ...typography.bodySmall, minHeight: 18, }, -}); \ No newline at end of file +}); diff --git a/mobile/src/components/EventCard.tsx b/mobile/src/components/EventCard.tsx index 696c41c..a12df44 100644 --- a/mobile/src/components/EventCard.tsx +++ b/mobile/src/components/EventCard.tsx @@ -8,7 +8,7 @@ import { formatTimestamp } from '../lib/utils'; import { tsToMs } from '../lib/time'; import { useCountdown } from '../hooks/useCountdown'; import type { Event } from '../types'; -import { useRouter } from "expo-router"; +import { useRouter } from 'expo-router'; interface EventCardProps { event: Event; @@ -17,7 +17,12 @@ interface EventCardProps { onEdit?: (event: Event) => void; } -export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCardProps) { +export function EventCard({ + event, + onPress, + isAdmin = false, + onEdit, +}: EventCardProps) { const { colors } = useTheme(); const [expanded, setExpanded] = useState(false); const router = useRouter(); @@ -32,7 +37,7 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard // Pass null if shouldn't show countdown to ensure hook resets const { remainingMs, hours, minutes, seconds, isElapsed } = useCountdown( - shouldShowCountdown && expiryMs ? expiryMs : null + shouldShowCountdown && expiryMs ? expiryMs : null, ); const toggleExpand = () => { @@ -42,15 +47,29 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard // Calculate progress for countdown bar const progress = React.useMemo(() => { - if (!shouldShowCountdown || !expiryMs || !startMs || !(remainingMs > 0)) return 0; + if (!shouldShowCountdown || !expiryMs || !startMs || !(remainingMs > 0)) + return 0; const totalDurationMs = (event.duration ?? 30) * 60 * 1000; if (totalDurationMs <= 0) return 0; return Math.max(0, Math.min(1, remainingMs / totalDurationMs)); - }, [shouldShowCountdown, remainingMs, expiryMs, startMs, event.duration, event.name]); + }, [ + shouldShowCountdown, + remainingMs, + expiryMs, + startMs, + event.duration, + event.name, + ]); // Auto-close event when timer expires (admin only) React.useEffect(() => { - if (isAdmin && isElapsed && event.status === 'open' && event.id && shouldShowCountdown) { + if ( + isAdmin && + isElapsed && + event.status === 'open' && + event.id && + shouldShowCountdown + ) { import('../lib/firebase/events').then(({ updateEventStatus }) => { updateEventStatus(event.id, 'closed').catch(console.error); }); @@ -59,133 +78,137 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard // Don't show expired open events to students if (isElapsed && !isAdmin && event.status === 'open') return null; - - const styles = React.useMemo(() => StyleSheet.create({ - card: { - backgroundColor: colors.surface, - borderRadius: borderRadius.md, - marginBottom: spacing.md, - overflow: 'hidden', - borderWidth: 1, - borderColor: colors.border.light, - }, - image: { - width: '100%', - height: 200, - resizeMode: 'cover', - }, - content: { - padding: spacing.lg, - }, - title: { - ...typography.h5, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - subtitle: { - ...typography.bodySmall, - color: colors.text.secondary, - marginBottom: spacing.sm, - }, - countdownContainer: { - marginBottom: spacing.sm, - }, - countdownText: { - ...typography.bodySmall, - color: colors.error, - fontWeight: '600', - marginBottom: spacing.xs, - }, - progressBarBg: { - height: 8, - backgroundColor: colors.border.light, - borderRadius: borderRadius.sm, - overflow: 'hidden', - }, - progressBarFill: { - height: '100%', - borderRadius: borderRadius.sm, - }, - foodPreview: { - marginTop: spacing.sm, - }, - foodItem: { - ...typography.body, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - moreItems: { - ...typography.bodySmall, - color: colors.text.secondary, - fontStyle: 'italic', - }, - expandedContent: { - marginTop: spacing.md, - }, - divider: { - height: 1, - backgroundColor: colors.border.light, - marginBottom: spacing.md, - }, - infoRow: { - marginBottom: spacing.sm, - }, - infoLabel: { - ...typography.bodySmall, - color: colors.text.secondary, - fontWeight: '600', - }, - infoValue: { - ...typography.body, - color: colors.text.primary, - marginTop: spacing.xs / 2, - }, - foodList: { - marginTop: spacing.md, - }, - sectionLabel: { - ...typography.body, - color: colors.text.secondary, - fontWeight: '600', - marginBottom: spacing.xs, - }, - foodDetailItem: { - ...typography.body, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - editButton: { - marginTop: spacing.lg, - backgroundColor: colors.primary, - paddingVertical: spacing.md, - paddingHorizontal: spacing.lg, - borderRadius: borderRadius.sm, - alignItems: 'center', - }, - editButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - expandIndicator: { - ...typography.caption, - color: colors.text.secondary, - textAlign: 'center', - marginTop: spacing.md, - }, - reviewButton: { - marginTop: spacing.md, - backgroundColor: colors.primary, - paddingVertical: spacing.md, - borderRadius: borderRadius.sm, - alignItems: 'center', - }, - reviewButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - }), [colors]); + + const styles = React.useMemo( + () => + StyleSheet.create({ + card: { + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + marginBottom: spacing.md, + overflow: 'hidden', + borderWidth: 1, + borderColor: colors.border.light, + }, + image: { + width: '100%', + height: 200, + resizeMode: 'cover', + }, + content: { + padding: spacing.lg, + }, + title: { + ...typography.h5, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + subtitle: { + ...typography.bodySmall, + color: colors.text.secondary, + marginBottom: spacing.sm, + }, + countdownContainer: { + marginBottom: spacing.sm, + }, + countdownText: { + ...typography.bodySmall, + color: colors.error, + fontWeight: '600', + marginBottom: spacing.xs, + }, + progressBarBg: { + height: 8, + backgroundColor: colors.border.light, + borderRadius: borderRadius.sm, + overflow: 'hidden', + }, + progressBarFill: { + height: '100%', + borderRadius: borderRadius.sm, + }, + foodPreview: { + marginTop: spacing.sm, + }, + foodItem: { + ...typography.body, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + moreItems: { + ...typography.bodySmall, + color: colors.text.secondary, + fontStyle: 'italic', + }, + expandedContent: { + marginTop: spacing.md, + }, + divider: { + height: 1, + backgroundColor: colors.border.light, + marginBottom: spacing.md, + }, + infoRow: { + marginBottom: spacing.sm, + }, + infoLabel: { + ...typography.bodySmall, + color: colors.text.secondary, + fontWeight: '600', + }, + infoValue: { + ...typography.body, + color: colors.text.primary, + marginTop: spacing.xs / 2, + }, + foodList: { + marginTop: spacing.md, + }, + sectionLabel: { + ...typography.body, + color: colors.text.secondary, + fontWeight: '600', + marginBottom: spacing.xs, + }, + foodDetailItem: { + ...typography.body, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + editButton: { + marginTop: spacing.lg, + backgroundColor: colors.primary, + paddingVertical: spacing.md, + paddingHorizontal: spacing.lg, + borderRadius: borderRadius.sm, + alignItems: 'center', + }, + editButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + expandIndicator: { + ...typography.caption, + color: colors.text.secondary, + textAlign: 'center', + marginTop: spacing.md, + }, + reviewButton: { + marginTop: spacing.md, + backgroundColor: colors.primary, + paddingVertical: spacing.md, + borderRadius: borderRadius.sm, + alignItems: 'center', + }, + reviewButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + }), + [colors], + ); return ( @@ -202,20 +225,28 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard {/* Location & Time */} - 📍 {event.Location?.name || event.host} • {formatTimestamp(event.foodAvailable)} + 📍 {event.Location?.name || event.host} •{' '} + {formatTimestamp(event.foodAvailable)} {/* Countdown Bar */} {shouldShowCountdown && !!expiryMs && !isElapsed && ( - ⏰ {hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}` : minutes}:{seconds.toString().padStart(2, '0')} left + ⏰{' '} + {hours > 0 + ? `${hours}:${minutes.toString().padStart(2, '0')}` + : minutes} + :{seconds.toString().padStart(2, '0')} left @@ -225,11 +256,14 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard {/* Food Items Preview */} {event.foods && event.foods.length > 0 && !expanded && ( - {event.foods.slice(0, 2).filter(f => f.item?.trim()).map((food, i) => ( - - • {food.item} ({food.quantity} {food.unit}) - - ))} + {event.foods + .slice(0, 2) + .filter((f) => f.item?.trim()) + .map((food, i) => ( + + • {food.item} ({food.quantity} {food.unit}) + + ))} {event.foods.length > 2 && ( +{event.foods.length - 2} more @@ -255,11 +289,13 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard {event.foods && event.foods.length > 0 && ( Available Food: - {event.foods.filter(f => f.item?.trim()).map((food, i) => ( - - • {food.item} ({food.quantity} {food.unit}) - - ))} + {event.foods + .filter((f) => f.item?.trim()) + .map((food, i) => ( + + • {food.item} ({food.quantity} {food.unit}) + + ))} )} @@ -267,18 +303,29 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard {isAdmin && ( <> {onEdit && ( - onEdit(event)}> + onEdit(event)} + > ✏️ Edit Event )} - {event.status === "closed" && ( + {event.status === 'closed' && ( { console.log('Navigating to event:', event.id); - console.log('Full pathname:', `/(admin)/reviews/${event.id}`); - router.push(`/(admin)/reviews/${event.id}?eventName=${encodeURIComponent(event.name)}`); + console.log( + 'Full pathname:', + `/(admin)/reviews/${event.id}`, + ); + router.push( + `/(admin)/reviews/${event.id}?eventName=${encodeURIComponent(event.name)}`, + ); }} > 💬 View Feedback @@ -299,7 +346,6 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard )} - {/* Expand Indicator */} {expanded ? '▲ Tap to collapse' : '▼ Tap for details'} @@ -311,21 +357,25 @@ export function EventCard({ event, onPress, isAdmin = false, onEdit }: EventCard function InfoRow({ label, value }: { label: string; value: string }) { const { colors } = useTheme(); - const styles = React.useMemo(() => StyleSheet.create({ - infoRow: { - marginBottom: spacing.sm, - }, - infoLabel: { - ...typography.bodySmall, - color: colors.text.secondary, - fontWeight: '600', - }, - infoValue: { - ...typography.body, - color: colors.text.primary, - marginTop: spacing.xs / 2, - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + infoRow: { + marginBottom: spacing.sm, + }, + infoLabel: { + ...typography.bodySmall, + color: colors.text.secondary, + fontWeight: '600', + }, + infoValue: { + ...typography.body, + color: colors.text.primary, + marginTop: spacing.xs / 2, + }, + }), + [colors], + ); return ( diff --git a/mobile/src/components/EventEditorModal.tsx b/mobile/src/components/EventEditorModal.tsx index 3e698ed..ecf0271 100644 --- a/mobile/src/components/EventEditorModal.tsx +++ b/mobile/src/components/EventEditorModal.tsx @@ -1,8 +1,8 @@ // src/components/EventEditorModal.tsx -import * as ImagePicker from "expo-image-picker"; -import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage"; +import * as ImagePicker from 'expo-image-picker'; +import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage'; import React, { useState, useEffect } from 'react'; -import { storage } from "../lib/firebase/config"; +import { storage } from '../lib/firebase/config'; import { View, Text, @@ -16,7 +16,9 @@ import { Alert, Image, } from 'react-native'; -import DateTimePicker, { DateTimePickerAndroid } from '@react-native-community/datetimepicker'; +import DateTimePicker, { + DateTimePickerAndroid, +} from '@react-native-community/datetimepicker'; import { useTheme } from '../lib/ThemeProvider'; import { typography, spacing, borderRadius } from '../lib/theme'; import type { Event, EventStatus, FoodItem } from '../types'; @@ -41,7 +43,13 @@ interface EventEditorModalProps { const MAX_FOOD_DURATION_HOURS = 4; const MAX_FOOD_DURATION_MINUTES = MAX_FOOD_DURATION_HOURS * 60; -export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: EventEditorModalProps) { +export function EventEditorModal({ + visible, + event, + onClose, + onSave, + onCreate, +}: EventEditorModalProps) { const { colors } = useTheme(); const [name, setName] = useState(''); const [host, setHost] = useState(''); @@ -65,11 +73,15 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: // Location preset picker const [showLocationDropdown, setShowLocationDropdown] = useState(false); - const [selectedPresetIndex, setSelectedPresetIndex] = useState(null); + const [selectedPresetIndex, setSelectedPresetIndex] = useState( + null, + ); // Calculate max available time (4 hours after arrival) const maxAvailableTime = React.useMemo(() => { - return new Date(foodArrived.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000); + return new Date( + foodArrived.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000, + ); }, [foodArrived]); // Calculate max duration based on arrival and pickup times @@ -95,9 +107,15 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: setFoods(event.foods || []); const eventImages = event.images || []; - const defaultImageUris = DEFAULT_IMAGES.map(img => Image.resolveAssetSource(img).uri); - const defaultSelected = eventImages.filter(img => defaultImageUris.includes(img)); - const uploadedSelected = eventImages.filter(img => !defaultImageUris.includes(img)); + const defaultImageUris = DEFAULT_IMAGES.map( + (img) => Image.resolveAssetSource(img).uri, + ); + const defaultSelected = eventImages.filter((img) => + defaultImageUris.includes(img), + ); + const uploadedSelected = eventImages.filter( + (img) => !defaultImageUris.includes(img), + ); setImages(defaultSelected); setUploadedImages(uploadedSelected); @@ -117,10 +135,11 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: } // Infer preset from incoming event (if exact match) - const matched = PRESET_LOCATIONS.findIndex(p => - p.name === (event.Location?.name || '') && - p.address === (event.Location?.address || '') && - p.campus_section === (event.Location?.campus_section || '') + const matched = PRESET_LOCATIONS.findIndex( + (p) => + p.name === (event.Location?.name || '') && + p.address === (event.Location?.address || '') && + p.campus_section === (event.Location?.campus_section || ''), ); setSelectedPresetIndex(matched >= 0 ? matched : null); } @@ -131,7 +150,7 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: if (durationNum > maxDuration) { Alert.alert( 'Duration Limit Exceeded', - `Maximum duration is ${maxDuration} minutes. Food cannot be given more than ${MAX_FOOD_DURATION_HOURS} hours after arrival.` + `Maximum duration is ${maxDuration} minutes. Food cannot be given more than ${MAX_FOOD_DURATION_HOURS} hours after arrival.`, ); setDuration(String(maxDuration)); } else { @@ -149,7 +168,7 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: if (durationNum > maxDuration) { Alert.alert( 'Error', - `Duration cannot exceed ${maxDuration} minutes due to food safety regulations (max ${MAX_FOOD_DURATION_HOURS} hours from arrival)` + `Duration cannot exceed ${maxDuration} minutes due to food safety regulations (max ${MAX_FOOD_DURATION_HOURS} hours from arrival)`, ); return; } @@ -171,8 +190,8 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: notes: notes.trim(), duration: durationNum, status, - foods: foods.filter(f => f.item?.trim()), - images: [...images, ...uploadedImages].filter(img => img.trim()), + foods: foods.filter((f) => f.item?.trim()), + images: [...images, ...uploadedImages].filter((img) => img.trim()), foodArrived: Timestamp.fromDate(foodArrived), foodAvailable: Timestamp.fromDate(foodAvailable), }; @@ -192,10 +211,17 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: }; const addFoodItem = () => { - setFoods([...foods, { id: Date.now().toString(), item: '', quantity: '', unit: 'pieces' }]); + setFoods([ + ...foods, + { id: Date.now().toString(), item: '', quantity: '', unit: 'pieces' }, + ]); }; - const updateFoodItem = (index: number, field: keyof FoodItem, value: string) => { + const updateFoodItem = ( + index: number, + field: keyof FoodItem, + value: string, + ) => { const updated = [...foods]; updated[index] = { ...updated[index], [field]: value }; setFoods(updated); @@ -211,10 +237,13 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: const toggleDefaultImage = (imgUri: string) => { if (images.includes(imgUri)) { - setImages(images.filter(img => img !== imgUri)); + setImages(images.filter((img) => img !== imgUri)); } else { if (!canSelectMoreImages()) { - Alert.alert("Limit Reached", "You can only select up to 2 images total."); + Alert.alert( + 'Limit Reached', + 'You can only select up to 2 images total.', + ); return; } setImages([...images, imgUri]); @@ -235,7 +264,9 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: } if (!selectedDate) return; - const maxAvailable = new Date(selectedDate.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000); + const maxAvailable = new Date( + selectedDate.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000, + ); setFoodArrived(selectedDate); if (foodAvailable > maxAvailable) { @@ -243,7 +274,7 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: setTimeout(() => { Alert.alert( 'Pickup Time Adjusted', - `Pickup time was adjusted to ${MAX_FOOD_DURATION_HOURS} hours after arrival (food safety limit)` + `Pickup time was adjusted to ${MAX_FOOD_DURATION_HOURS} hours after arrival (food safety limit)`, ); }, 100); } else if (foodAvailable < selectedDate) { @@ -267,12 +298,14 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: return; } - const maxAvailable = new Date(foodArrived.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000); + const maxAvailable = new Date( + foodArrived.getTime() + MAX_FOOD_DURATION_HOURS * 60 * 60 * 1000, + ); if (selectedDate > maxAvailable) { setTimeout(() => { Alert.alert( 'Time Limit Exceeded', - `Pickup time cannot be more than ${MAX_FOOD_DURATION_HOURS} hours after arrival (food safety limit)` + `Pickup time cannot be more than ${MAX_FOOD_DURATION_HOURS} hours after arrival (food safety limit)`, ); }, 100); return; @@ -282,13 +315,17 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: const handleAddImage = async () => { if (!canSelectMoreImages()) { - Alert.alert("Limit Reached", "You can only select up to 2 images total."); + Alert.alert('Limit Reached', 'You can only select up to 2 images total.'); return; } try { - const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); - if (permission.status !== "granted") { - Alert.alert("Permission Required", "We need access to your photos to upload images."); + const permission = + await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (permission.status !== 'granted') { + Alert.alert( + 'Permission Required', + 'We need access to your photos to upload images.', + ); return; } @@ -317,8 +354,11 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: } setUploadedImages((prev) => [...prev, ...uploadedUrls]); } catch (error) { - console.error("Error uploading image(s):", error); - Alert.alert("Upload Failed", "Could not upload one or more images. Please try again."); + console.error('Error uploading image(s):', error); + Alert.alert( + 'Upload Failed', + 'Could not upload one or more images. Please try again.', + ); } }; @@ -352,380 +392,384 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: setCampusSection(v); }; - const styles = React.useMemo(() => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: colors.background, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: spacing.lg, - paddingTop: spacing.xxl + 20, - backgroundColor: colors.surface, - borderBottomWidth: 1, - borderBottomColor: colors.border.light, - }, - headerTitle: { - ...typography.h4, - color: colors.text.primary, - }, - closeButton: { - padding: spacing.sm, - }, - closeButtonText: { - ...typography.h4, - color: colors.text.secondary, - }, - scrollView: { - flex: 1, - }, - scrollContent: { - padding: spacing.lg, - }, - sectionTitle: { - ...typography.h6, - color: colors.text.primary, - marginTop: spacing.lg, - marginBottom: spacing.md, - }, - label: { - ...typography.bodySmall, - color: colors.text.secondary, - fontWeight: '600', - marginBottom: spacing.xs, - }, - helperText: { - ...typography.caption, - color: colors.text.secondary, - marginBottom: spacing.xs, - fontStyle: 'italic', - }, - input: { - ...typography.body, - color: colors.text.primary, - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: borderRadius.sm, - padding: spacing.md, - marginBottom: spacing.md, - }, - textArea: { - minHeight: 80, - textAlignVertical: 'top', - }, - dateButton: { - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: borderRadius.sm, - padding: spacing.md, - marginBottom: spacing.md, - }, - dateButtonText: { - ...typography.body, - color: colors.text.primary, - }, - doneButton: { - backgroundColor: colors.primary, - padding: spacing.sm, - borderRadius: borderRadius.sm, - alignItems: 'center', - marginBottom: spacing.md, - }, - doneButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - statusContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: spacing.sm, - marginBottom: spacing.md, - }, - statusChip: { - paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, - borderRadius: borderRadius.sm, - borderWidth: 1, - borderColor: colors.border.default, - backgroundColor: colors.surface, - }, - statusChipActive: { - backgroundColor: colors.primary, - borderColor: colors.primary, - }, - statusChipText: { - ...typography.bodySmall, - color: colors.text.secondary, - }, - statusChipTextActive: { - color: colors.text.onPrimary, - fontWeight: '600', - }, - foodItemContainer: { - marginBottom: spacing.md, - }, - foodItemRow: { - flexDirection: 'row', - gap: spacing.sm, - alignItems: 'center', - }, - imageRow: { - flexDirection: 'row', - alignItems: 'center', - gap: spacing.md, - paddingVertical: spacing.sm, - }, - imageItem: { - position: 'relative', - }, - imagePreview: { - width: 120, - height: 120, - borderRadius: borderRadius.md, - }, - deleteButton: { - position: 'absolute', - top: 4, - right: 4, - backgroundColor: colors.error, - borderRadius: 12, - width: 24, - height: 24, - justifyContent: 'center', - alignItems: 'center', - }, - deleteText: { - color: colors.text.onPrimary, - fontSize: 16, - fontWeight: 'bold', - }, - removeButton: { - padding: spacing.sm, - backgroundColor: colors.error, - borderRadius: borderRadius.sm, - justifyContent: 'center', - alignItems: 'center', - width: 40, - }, - removeButtonText: { - color: colors.text.onPrimary, - fontSize: 20, - }, - addButton: { - padding: spacing.md, - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: borderRadius.sm, - alignItems: 'center', - marginBottom: spacing.md, - }, - addButtonText: { - ...typography.body, - color: colors.primary, - fontWeight: '600', - }, - footer: { - flexDirection: 'row', - gap: spacing.md, - padding: spacing.lg, - backgroundColor: colors.surface, - borderTopWidth: 1, - borderTopColor: colors.border.light, - }, - footerButton: { - flex: 1, - padding: spacing.lg, - borderRadius: borderRadius.sm, - alignItems: 'center', - }, - cancelButton: { - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border.default, - }, - cancelButtonText: { - ...typography.body, - color: colors.text.secondary, - fontWeight: '600', - }, - saveButton: { - backgroundColor: colors.primary, - }, - saveButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - defaultImageGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: spacing.sm, - marginBottom: spacing.md, - }, - defaultImageWrapper: { - width: '48%', - aspectRatio: 1, - borderRadius: borderRadius.md, - overflow: 'hidden', - borderWidth: 3, - borderColor: 'transparent', - backgroundColor: colors.surface, - }, - selectedDefaultImage: { - borderColor: colors.primary, - }, - defaultImage: { - width: '100%', - height: '100%', - resizeMode: 'cover', - }, - selectedImagesContainer: { - marginBottom: spacing.lg, - }, - imageSection: { - marginBottom: spacing.lg, - }, - subsectionTitle: { - ...typography.body, - color: colors.text.primary, - fontWeight: '600', - marginBottom: spacing.sm, - }, - imageLabel: { - position: 'absolute', - bottom: 4, - left: 4, - backgroundColor: colors.primary, - paddingHorizontal: spacing.sm, - paddingVertical: 2, - borderRadius: borderRadius.sm, - }, - imageLabelText: { - ...typography.caption, - color: colors.text.onPrimary, - fontWeight: '600', - fontSize: 10, - }, - selectedOverlay: { - position: 'absolute', - top: 4, - right: 4, - backgroundColor: colors.primary, - borderRadius: 12, - width: 24, - height: 24, - justifyContent: 'center', - alignItems: 'center', - }, - selectedCheckmark: { - color: colors.text.onPrimary, - fontSize: 16, - fontWeight: 'bold', - }, - addButtonDisabled: { - backgroundColor: colors.surface, - borderColor: colors.border.light, - opacity: 0.5, - }, - addButtonTextDisabled: { - color: colors.text.secondary, - }, - - // Preset UI - presetButtonRow: { - flexDirection: 'row', - alignItems: 'center', - gap: spacing.sm, - marginBottom: spacing.xs, - }, - presetButton: { - alignSelf: 'flex-start', - backgroundColor: colors.surface, - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: borderRadius.sm, - paddingVertical: spacing.sm, - paddingHorizontal: spacing.md, - }, - presetButtonText: { - ...typography.bodySmall, - color: colors.primary, - fontWeight: '600', - }, - presetBadge: { - backgroundColor: colors.primary, - paddingHorizontal: spacing.sm, - paddingVertical: 2, - borderRadius: borderRadius.sm, - }, - presetBadgeText: { - ...typography.caption, - color: colors.text.onPrimary, - fontWeight: '600', - }, - presetModalBackdrop: { - flex: 1, - backgroundColor: '#00000066', - }, - presetModalSheet: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - maxHeight: '70%', - backgroundColor: colors.surface, - borderTopLeftRadius: borderRadius.md, - borderTopRightRadius: borderRadius.md, - padding: spacing.lg, - borderTopWidth: 1, - borderColor: colors.border.light, - }, - presetModalTitle: { - ...typography.h6, - color: colors.text.primary, - marginBottom: spacing.md, - }, - presetList: { - marginBottom: spacing.md, - }, - presetOption: { - borderWidth: 1, - borderColor: colors.border.default, - borderRadius: borderRadius.sm, - padding: spacing.md, - marginBottom: spacing.sm, - backgroundColor: colors.surface, - }, - presetOptionSelected: { - borderColor: colors.primary, - }, - presetOptionLabel: { - ...typography.body, - color: colors.text.primary, - fontWeight: '600', - marginBottom: 2, - }, - presetOptionSub: { - ...typography.caption, - color: colors.text.secondary, - marginBottom: 2, - }, - presetOptionMeta: { - ...typography.caption, - color: colors.text.secondary, - fontStyle: 'italic', - }, - presetActions: { - flexDirection: 'row', - gap: spacing.md, - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: spacing.lg, + paddingTop: spacing.xxl + 20, + backgroundColor: colors.surface, + borderBottomWidth: 1, + borderBottomColor: colors.border.light, + }, + headerTitle: { + ...typography.h4, + color: colors.text.primary, + }, + closeButton: { + padding: spacing.sm, + }, + closeButtonText: { + ...typography.h4, + color: colors.text.secondary, + }, + scrollView: { + flex: 1, + }, + scrollContent: { + padding: spacing.lg, + }, + sectionTitle: { + ...typography.h6, + color: colors.text.primary, + marginTop: spacing.lg, + marginBottom: spacing.md, + }, + label: { + ...typography.bodySmall, + color: colors.text.secondary, + fontWeight: '600', + marginBottom: spacing.xs, + }, + helperText: { + ...typography.caption, + color: colors.text.secondary, + marginBottom: spacing.xs, + fontStyle: 'italic', + }, + input: { + ...typography.body, + color: colors.text.primary, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: borderRadius.sm, + padding: spacing.md, + marginBottom: spacing.md, + }, + textArea: { + minHeight: 80, + textAlignVertical: 'top', + }, + dateButton: { + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: borderRadius.sm, + padding: spacing.md, + marginBottom: spacing.md, + }, + dateButtonText: { + ...typography.body, + color: colors.text.primary, + }, + doneButton: { + backgroundColor: colors.primary, + padding: spacing.sm, + borderRadius: borderRadius.sm, + alignItems: 'center', + marginBottom: spacing.md, + }, + doneButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + statusContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: spacing.sm, + marginBottom: spacing.md, + }, + statusChip: { + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + borderRadius: borderRadius.sm, + borderWidth: 1, + borderColor: colors.border.default, + backgroundColor: colors.surface, + }, + statusChipActive: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + statusChipText: { + ...typography.bodySmall, + color: colors.text.secondary, + }, + statusChipTextActive: { + color: colors.text.onPrimary, + fontWeight: '600', + }, + foodItemContainer: { + marginBottom: spacing.md, + }, + foodItemRow: { + flexDirection: 'row', + gap: spacing.sm, + alignItems: 'center', + }, + imageRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.md, + paddingVertical: spacing.sm, + }, + imageItem: { + position: 'relative', + }, + imagePreview: { + width: 120, + height: 120, + borderRadius: borderRadius.md, + }, + deleteButton: { + position: 'absolute', + top: 4, + right: 4, + backgroundColor: colors.error, + borderRadius: 12, + width: 24, + height: 24, + justifyContent: 'center', + alignItems: 'center', + }, + deleteText: { + color: colors.text.onPrimary, + fontSize: 16, + fontWeight: 'bold', + }, + removeButton: { + padding: spacing.sm, + backgroundColor: colors.error, + borderRadius: borderRadius.sm, + justifyContent: 'center', + alignItems: 'center', + width: 40, + }, + removeButtonText: { + color: colors.text.onPrimary, + fontSize: 20, + }, + addButton: { + padding: spacing.md, + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: borderRadius.sm, + alignItems: 'center', + marginBottom: spacing.md, + }, + addButtonText: { + ...typography.body, + color: colors.primary, + fontWeight: '600', + }, + footer: { + flexDirection: 'row', + gap: spacing.md, + padding: spacing.lg, + backgroundColor: colors.surface, + borderTopWidth: 1, + borderTopColor: colors.border.light, + }, + footerButton: { + flex: 1, + padding: spacing.lg, + borderRadius: borderRadius.sm, + alignItems: 'center', + }, + cancelButton: { + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border.default, + }, + cancelButtonText: { + ...typography.body, + color: colors.text.secondary, + fontWeight: '600', + }, + saveButton: { + backgroundColor: colors.primary, + }, + saveButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + defaultImageGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: spacing.sm, + marginBottom: spacing.md, + }, + defaultImageWrapper: { + width: '48%', + aspectRatio: 1, + borderRadius: borderRadius.md, + overflow: 'hidden', + borderWidth: 3, + borderColor: 'transparent', + backgroundColor: colors.surface, + }, + selectedDefaultImage: { + borderColor: colors.primary, + }, + defaultImage: { + width: '100%', + height: '100%', + resizeMode: 'cover', + }, + selectedImagesContainer: { + marginBottom: spacing.lg, + }, + imageSection: { + marginBottom: spacing.lg, + }, + subsectionTitle: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + marginBottom: spacing.sm, + }, + imageLabel: { + position: 'absolute', + bottom: 4, + left: 4, + backgroundColor: colors.primary, + paddingHorizontal: spacing.sm, + paddingVertical: 2, + borderRadius: borderRadius.sm, + }, + imageLabelText: { + ...typography.caption, + color: colors.text.onPrimary, + fontWeight: '600', + fontSize: 10, + }, + selectedOverlay: { + position: 'absolute', + top: 4, + right: 4, + backgroundColor: colors.primary, + borderRadius: 12, + width: 24, + height: 24, + justifyContent: 'center', + alignItems: 'center', + }, + selectedCheckmark: { + color: colors.text.onPrimary, + fontSize: 16, + fontWeight: 'bold', + }, + addButtonDisabled: { + backgroundColor: colors.surface, + borderColor: colors.border.light, + opacity: 0.5, + }, + addButtonTextDisabled: { + color: colors.text.secondary, + }, + + // Preset UI + presetButtonRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm, + marginBottom: spacing.xs, + }, + presetButton: { + alignSelf: 'flex-start', + backgroundColor: colors.surface, + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: borderRadius.sm, + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md, + }, + presetButtonText: { + ...typography.bodySmall, + color: colors.primary, + fontWeight: '600', + }, + presetBadge: { + backgroundColor: colors.primary, + paddingHorizontal: spacing.sm, + paddingVertical: 2, + borderRadius: borderRadius.sm, + }, + presetBadgeText: { + ...typography.caption, + color: colors.text.onPrimary, + fontWeight: '600', + }, + presetModalBackdrop: { + flex: 1, + backgroundColor: '#00000066', + }, + presetModalSheet: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + maxHeight: '70%', + backgroundColor: colors.surface, + borderTopLeftRadius: borderRadius.md, + borderTopRightRadius: borderRadius.md, + padding: spacing.lg, + borderTopWidth: 1, + borderColor: colors.border.light, + }, + presetModalTitle: { + ...typography.h6, + color: colors.text.primary, + marginBottom: spacing.md, + }, + presetList: { + marginBottom: spacing.md, + }, + presetOption: { + borderWidth: 1, + borderColor: colors.border.default, + borderRadius: borderRadius.sm, + padding: spacing.md, + marginBottom: spacing.sm, + backgroundColor: colors.surface, + }, + presetOptionSelected: { + borderColor: colors.primary, + }, + presetOptionLabel: { + ...typography.body, + color: colors.text.primary, + fontWeight: '600', + marginBottom: 2, + }, + presetOptionSub: { + ...typography.caption, + color: colors.text.secondary, + marginBottom: 2, + }, + presetOptionMeta: { + ...typography.caption, + color: colors.text.secondary, + fontStyle: 'italic', + }, + presetActions: { + flexDirection: 'row', + gap: spacing.md, + }, + }), + [colors], + ); return ( @@ -742,7 +786,10 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: - + {/* Basic Info */} Basic Information @@ -821,7 +868,8 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: Available for Pickup - Must be within {MAX_FOOD_DURATION_HOURS} hours of arrival time (food safety) + Must be within {MAX_FOOD_DURATION_HOURS} hours of arrival time (food + safety) - Food must be closed within {MAX_FOOD_DURATION_HOURS} hours of arrival. - Current gap between arrival and pickup: {Math.floor((foodAvailable.getTime() - foodArrived.getTime()) / 60000)} minutes + Food must be closed within {MAX_FOOD_DURATION_HOURS} hours of + arrival. Current gap between arrival and pickup:{' '} + {Math.floor( + (foodAvailable.getTime() - foodArrived.getTime()) / 60000, + )}{' '} + minutes - {selectedPresetIndex !== null ? 'Change preset' : 'Choose from presets (optional)'} + {selectedPresetIndex !== null + ? 'Change preset' + : 'Choose from presets (optional)'} {selectedPresetIndex !== null && ( @@ -911,7 +965,8 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: )} - You can still type a custom location below even after choosing a preset. + You can still type a custom location below even after choosing a + preset. Location Name @@ -957,22 +1012,27 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: Status - {(['drafted', 'saved', 'open', 'closed'] as EventStatus[]).map((s) => ( - setStatus(s)} - > - ( + setStatus(s)} > - {s} - - - ))} + + {s} + + + ), + )} Notes @@ -1001,7 +1061,9 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: updateFoodItem(index, 'quantity', text)} + onChangeText={(text) => + updateFoodItem(index, 'quantity', text) + } placeholder="Qty" placeholderTextColor={colors.text.secondary} /> @@ -1028,7 +1090,8 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: {/* Images */} Images - Select up to 2 images total (default + uploaded). Current: {getTotalSelectedImages()}/2 + Select up to 2 images total (default + uploaded). Current:{' '} + {getTotalSelectedImages()}/2 {(images.length > 0 || uploadedImages.length > 0) && ( @@ -1047,7 +1110,9 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: setImages(images.filter((_, i) => i !== index))} + onPress={() => + setImages(images.filter((_, i) => i !== index)) + } > @@ -1074,7 +1139,9 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: {/* Default Images Section */} Default Images - Tap to select from default options + + Tap to select from default options + {DEFAULT_IMAGES.map((img, index) => { const imgUri = Image.resolveAssetSource(img).uri; @@ -1109,15 +1176,17 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: - + {canSelectMoreImages() ? 'Upload Photo' : 'Limit Reached'} @@ -1150,7 +1219,10 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: animationType="fade" onRequestClose={closePresetPicker} > - + @@ -1161,21 +1233,29 @@ export function EventEditorModal({ visible, event, onClose, onSave, onCreate }: key={p.label} style={[ styles.presetOption, - selectedPresetIndex === idx && styles.presetOptionSelected + selectedPresetIndex === idx && styles.presetOptionSelected, ]} onPress={() => applyPreset(idx)} > {p.label} {p.address} - Section: {p.campus_section} + + Section: {p.campus_section} + ))} - + Clear - + Done @@ -1201,4 +1281,4 @@ function formatDate(date: Date): string { minute: '2-digit', hour12: true, }); -} \ No newline at end of file +} diff --git a/mobile/src/components/LocationFilterDropdown.tsx b/mobile/src/components/LocationFilterDropdown.tsx index 90e0892..09844f0 100644 --- a/mobile/src/components/LocationFilterDropdown.tsx +++ b/mobile/src/components/LocationFilterDropdown.tsx @@ -106,7 +106,7 @@ export default function LocationFilterDropdown({ zIndex: 999, }, }), - [colors] + [colors], ); return ( @@ -156,12 +156,8 @@ export default function LocationFilterDropdown({ {isOpen && ( - setIsOpen(false)} - /> + setIsOpen(false)} /> )} ); } - diff --git a/mobile/src/components/NotificationsListener.tsx b/mobile/src/components/NotificationsListener.tsx index 55ecea0..fdd19e3 100644 --- a/mobile/src/components/NotificationsListener.tsx +++ b/mobile/src/components/NotificationsListener.tsx @@ -4,12 +4,18 @@ import { router } from 'expo-router'; export default function NotificationsListener() { useEffect(() => { - const sub = Notifications.addNotificationResponseReceivedListener((response) => { - const eventId = response?.notification?.request?.content?.data?.eventId as string | undefined; - if (eventId) { - router.push({ pathname: '/(student)', params: { focusEventId: eventId } }); - } - }); + const sub = Notifications.addNotificationResponseReceivedListener( + (response) => { + const eventId = response?.notification?.request?.content?.data + ?.eventId as string | undefined; + if (eventId) { + router.push({ + pathname: '/(student)', + params: { focusEventId: eventId }, + }); + } + }, + ); return () => sub.remove(); }, []); return null; diff --git a/mobile/src/components/NotificationsToggle.tsx b/mobile/src/components/NotificationsToggle.tsx index 5c11e57..b4426f7 100644 --- a/mobile/src/components/NotificationsToggle.tsx +++ b/mobile/src/components/NotificationsToggle.tsx @@ -11,7 +11,9 @@ interface NotificationsToggleProps { showLabel?: boolean; } -export default function NotificationsToggle({ showLabel = true }: NotificationsToggleProps) { +export default function NotificationsToggle({ + showLabel = true, +}: NotificationsToggleProps) { const { user, isLoaded } = useUser(); const { colors } = useTheme(); const [enabled, setEnabled] = useState(true); @@ -37,15 +39,19 @@ export default function NotificationsToggle({ showLabel = true }: NotificationsT if (value) { await registerForPushNotificationsAsync( uid, - user?.primaryEmailAddress?.emailAddress ?? undefined + user?.primaryEmailAddress?.emailAddress ?? undefined, ); } }; return ( - + {showLabel && ( - Notifications + + Notifications + )} {loading ? ( diff --git a/mobile/src/components/PushTokenRegistrar.tsx b/mobile/src/components/PushTokenRegistrar.tsx index d6d43df..2369c98 100644 --- a/mobile/src/components/PushTokenRegistrar.tsx +++ b/mobile/src/components/PushTokenRegistrar.tsx @@ -11,7 +11,7 @@ export default function PushTokenRegistrar() { if (!isSignedIn || !isLoaded || !user?.id) return; registerForPushNotificationsAsync( user.id, - user.primaryEmailAddress?.emailAddress ?? undefined + user.primaryEmailAddress?.emailAddress ?? undefined, ).catch(() => {}); }, [isSignedIn, isLoaded, user?.id]); diff --git a/mobile/src/components/SettingsScreen.tsx b/mobile/src/components/SettingsScreen.tsx index 7702691..ad5fa06 100644 --- a/mobile/src/components/SettingsScreen.tsx +++ b/mobile/src/components/SettingsScreen.tsx @@ -1,6 +1,15 @@ // src/components/SettingsScreen.tsx import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { View, Text, StyleSheet, ScrollView, Pressable, Switch, ActivityIndicator, Alert } from 'react-native'; +import { + View, + Text, + StyleSheet, + ScrollView, + Pressable, + Switch, + ActivityIndicator, + Alert, +} from 'react-native'; import { useAuth, useUser } from '@clerk/clerk-expo'; import { router } from 'expo-router'; import { doc, getDoc } from 'firebase/firestore'; @@ -25,7 +34,7 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const { signOut, getToken, isSignedIn } = useAuth(); const { user } = useUser(); const { colors, themeMode, toggleTheme } = useTheme(); - + const [name, setName] = useState(''); const [selectedLocations, setSelectedLocations] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -44,11 +53,11 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const loadUserPreferences = async () => { if (!user) return; - + try { const userRef = doc(firestore, 'Users', user.id); const userSnap = await getDoc(userRef); - + if (userSnap.exists()) { const userData = userSnap.data(); setName(userData.name || ''); @@ -97,7 +106,6 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { } }, [role, isSignedIn, loadAdminLists]); - const handleApprove = async (uid: string) => { try { await approveStaff(uid, getToken); @@ -112,7 +120,10 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const handleRevoke = async (uid: string) => { try { await revokeStaff(uid, getToken); - Alert.alert('Updated', 'Staff access has been revoked / request rejected.'); + Alert.alert( + 'Updated', + 'Staff access has been revoked / request rejected.', + ); await loadAdminLists(); } catch (err: any) { console.error('Failed to revoke staff:', err); @@ -130,58 +141,104 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const faqRoute = '/settings/faq'; // student FAQs (hidden from tab bar) return ( - - Settings + + Settings + {role === 'admin' ? 'Manage admin settings and staff access' : role === 'staff' - ? 'Manage your staff settings and preferences' - : 'Manage your settings and preferences'} + ? 'Manage your staff settings and preferences' + : 'Manage your settings and preferences'} {/* Profile */} - Profile + + Profile + - router.push(editNameRoute as any)} > - Display Name - {name || 'Not set'} + + Display Name + + + {name || 'Not set'} + - + - router.push(editLocationsRoute as any)} > - Campus Preferences + + Campus Preferences + - {selectedLocations.length > 0 ? selectedLocations.join(', ') : 'None selected'} + {selectedLocations.length > 0 + ? selectedLocations.join(', ') + : 'None selected'} - + {/* Admin-only staff management */} {role === 'admin' && ( <> - + Staff access requests - + {loadingAdminLists && pendingStaff.length === 0 ? ( - + Loading requests... @@ -190,41 +247,77 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { {pendingError} - - Retry + + + Retry + ) : pendingStaff.length === 0 ? ( - + No pending staff requests. ) : ( {pendingStaff.map((u) => ( - + - + {u.email || u.userId} - + Role: {u.role} • Status: {u.status} handleApprove(u.userId)} > - Approve + + Approve + handleRevoke(u.userId)} > - Revoke + + Revoke + @@ -233,15 +326,35 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { )} - + Existing staff - + {loadingAdminLists && activeStaff.length === 0 ? ( - + Loading staff... @@ -250,35 +363,61 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { {staffError} - - Retry + + + Retry + ) : activeStaff.length === 0 ? ( - + No active staff members yet. ) : ( {activeStaff.map((u) => ( - + - + {u.email || u.userId} - + Role: {u.role} • Status: {u.status} handleRevoke(u.userId)} > - Revoke + + Revoke + @@ -290,11 +429,25 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { )} {/* App Settings */} - App Settings + + App Settings + - + - Theme + + Theme + {themeMode === 'dark' ? 'Dark mode' : 'Light mode'} @@ -307,9 +460,16 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { /> - + - Notifications + + Notifications + Receive event updates @@ -320,16 +480,30 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { {/* Student-only FAQ redirect */} {role === 'student' && ( router.push(faqRoute as any)} > - Student FAQs - + + Student FAQs + + Common questions about using the app - + )} @@ -337,7 +511,9 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { style={[styles.signOutButton, { backgroundColor: colors.error }]} onPress={handleSignOut} > - Sign Out + + Sign Out + ); diff --git a/mobile/src/components/SignInWith.tsx b/mobile/src/components/SignInWith.tsx index bea2887..542d1bb 100644 --- a/mobile/src/components/SignInWith.tsx +++ b/mobile/src/components/SignInWith.tsx @@ -41,25 +41,37 @@ export default function SignInWith({ strategy }: SignInWithProps) { - - + - + - + + 24 48z" + /> Sign in with Google diff --git a/mobile/src/components/SortDropdown.tsx b/mobile/src/components/SortDropdown.tsx index c27ef09..c6b3a8e 100644 --- a/mobile/src/components/SortDropdown.tsx +++ b/mobile/src/components/SortDropdown.tsx @@ -12,7 +12,11 @@ type SortDropdownProps = { buttonSize?: number; }; -export default function SortDropdown({ currentSort, onSortChange, buttonSize = 32 }: SortDropdownProps) { +export default function SortDropdown({ + currentSort, + onSortChange, + buttonSize = 32, +}: SortDropdownProps) { const { colors } = useTheme(); const [isOpen, setIsOpen] = React.useState(false); @@ -21,63 +25,67 @@ export default function SortDropdown({ currentSort, onSortChange, buttonSize = 3 { value: 'expiry-asc', label: 'Ending Soon' }, ]; - const styles = React.useMemo(() => StyleSheet.create({ - container: { - position: 'relative', - justifyContent: 'center', - alignItems: 'center', - }, - button: { - backgroundColor: colors.surface, - alignItems: 'center', - justifyContent: 'center', - borderWidth: 1, - borderColor: colors.border.light, - borderRadius: borderRadius.sm, - }, - dropdown: { - position: 'absolute', - top: '100%', - right: 0, - backgroundColor: colors.surface, - borderRadius: borderRadius.md, - paddingVertical: spacing.xs, - minWidth: 180, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.2, - shadowRadius: 8, - elevation: 5, - zIndex: 1000, - marginTop: spacing.xs, - }, - option: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, - }, - optionSelected: { - backgroundColor: colors.primary + '11', - }, - optionText: { - ...typography.body, - color: colors.text.primary, - }, - optionTextSelected: { - color: colors.primary, - fontWeight: '600', - }, - overlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - zIndex: 999, - }, - }), [colors]); + const styles = React.useMemo( + () => + StyleSheet.create({ + container: { + position: 'relative', + justifyContent: 'center', + alignItems: 'center', + }, + button: { + backgroundColor: colors.surface, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: colors.border.light, + borderRadius: borderRadius.sm, + }, + dropdown: { + position: 'absolute', + top: '100%', + right: 0, + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + paddingVertical: spacing.xs, + minWidth: 180, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 8, + elevation: 5, + zIndex: 1000, + marginTop: spacing.xs, + }, + option: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + }, + optionSelected: { + backgroundColor: colors.primary + '11', + }, + optionText: { + ...typography.body, + color: colors.text.primary, + }, + optionTextSelected: { + color: colors.primary, + fontWeight: '600', + }, + overlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + zIndex: 999, + }, + }), + [colors], + ); return ( <> @@ -88,7 +96,11 @@ export default function SortDropdown({ currentSort, onSortChange, buttonSize = 3 style={[styles.button, { height: buttonSize, width: buttonSize }]} hitSlop={8} > - + {isOpen && ( @@ -104,9 +116,20 @@ export default function SortDropdown({ currentSort, onSortChange, buttonSize = 3 }} style={[styles.option, selected && styles.optionSelected]} > - {opt.label} + + {opt.label} + {selected && ( - + )} ); @@ -114,14 +137,10 @@ export default function SortDropdown({ currentSort, onSortChange, buttonSize = 3 )} - + {isOpen && ( - setIsOpen(false)} - /> + setIsOpen(false)} /> )} ); } - diff --git a/mobile/src/content/terms.ts b/mobile/src/content/terms.ts index 5ddf453..28ac0db 100644 --- a/mobile/src/content/terms.ts +++ b/mobile/src/content/terms.ts @@ -18,23 +18,28 @@ export const TERMS_AND_CONDITIONS = `(DUMMY) By using BU Catering, you agree to export const TERMS_SECTIONS = [ { title: 'Availability', - content: 'Events are subject to availability and may be cancelled without notice.', + content: + 'Events are subject to availability and may be cancelled without notice.', }, { title: 'Food Safety', - content: 'Food allergies and dietary restrictions are your responsibility. Always verify ingredients before consuming.', + content: + 'Food allergies and dietary restrictions are your responsibility. Always verify ingredients before consuming.', }, { title: 'Service Rights', - content: 'Event hosts reserve the right to limit quantities and refuse service.', + content: + 'Event hosts reserve the right to limit quantities and refuse service.', }, { title: 'User Conduct', - content: 'You agree to provide accurate information and use the app respectfully.', + content: + 'You agree to provide accurate information and use the app respectfully.', }, { title: 'Liability', - content: 'BU Catering is not liable for any health issues arising from food consumption.', + content: + 'BU Catering is not liable for any health issues arising from food consumption.', }, { title: 'Data Collection', @@ -44,4 +49,4 @@ export const TERMS_SECTIONS = [ title: 'Updates', content: 'These terms may be updated at any time.', }, -]; \ No newline at end of file +]; diff --git a/mobile/src/hooks/useCountdown.ts b/mobile/src/hooks/useCountdown.ts index d8a8010..ef12323 100644 --- a/mobile/src/hooks/useCountdown.ts +++ b/mobile/src/hooks/useCountdown.ts @@ -11,14 +11,14 @@ export function useCountdown(targetMs: number | null, tickMs = 1000) { setNow(Date.now()); return; } - + // Update immediately when targetMs changes setNow(Date.now()); - + const id = setInterval(() => { setNow(Date.now()); }, tickMs); - + return () => clearInterval(id); }, [targetMs, tickMs]); @@ -32,4 +32,4 @@ export function useCountdown(targetMs: number | null, tickMs = 1000) { const isElapsed = targetMs ? remainingMs <= 0 : true; return { remainingMs, ...parts, isElapsed }; -} \ No newline at end of file +} diff --git a/mobile/src/hooks/useEvents.ts b/mobile/src/hooks/useEvents.ts index 2068227..900a1c1 100644 --- a/mobile/src/hooks/useEvents.ts +++ b/mobile/src/hooks/useEvents.ts @@ -16,19 +16,20 @@ export function useOpenEvents() { const loadEvents = async () => { try { setLoading(true); - const { events: fetchedEvents } = await fetchOpenEventsPage({ pageSize: 50 }); - + const { events: fetchedEvents } = await fetchOpenEventsPage({ + pageSize: 50, + }); + // Sort by expiry time (soonest first) const sorted = fetchedEvents.sort((a, b) => { const expiryA = getEventExpiryTime(a); const expiryB = getEventExpiryTime(b); return expiryA - expiryB; }); - + setEvents(sorted); setError(null); } catch (err) { - setError('Failed to load events'); } finally { setLoading(false); @@ -54,15 +55,14 @@ export function useAllEvents() { const loadEvents = async () => { try { setLoading(true); - const { events: fetchedEvents } = await fetchEventsPage({ + const { events: fetchedEvents } = await fetchEventsPage({ pageSize: 100, order: 'foodAvailable', - direction: 'desc' + direction: 'desc', }); setEvents(fetchedEvents); setError(null); } catch (err) { - setError('Failed to load events'); } finally { setLoading(false); @@ -83,17 +83,19 @@ function getEventExpiryTime(event: Event): number { if (event.foodAvailable) { if (event.foodAvailable instanceof Timestamp) { startMs = event.foodAvailable.toMillis(); - } else if (typeof event.foodAvailable === 'object' && 'toDate' in event.foodAvailable) { + } else if ( + typeof event.foodAvailable === 'object' && + 'toDate' in event.foodAvailable + ) { startMs = event.foodAvailable.toDate().getTime(); } else if (typeof event.foodAvailable === 'number') { startMs = event.foodAvailable; } } - + const durationMs = (event.duration ?? 30) * 60 * 1000; return startMs + durationMs; } catch (error) { - return Date.now() + 30 * 60 * 1000; // Default to 30 minutes from now } -} \ No newline at end of file +} diff --git a/mobile/src/lib/ThemeProvider.tsx b/mobile/src/lib/ThemeProvider.tsx index a09e2df..87ff029 100644 --- a/mobile/src/lib/ThemeProvider.tsx +++ b/mobile/src/lib/ThemeProvider.tsx @@ -1,5 +1,11 @@ // Theme management provider and hook -import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import React, { + createContext, + useContext, + useState, + useEffect, + ReactNode, +} from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { lightColors, darkColors } from './theme'; @@ -56,7 +62,9 @@ export function ThemeProvider({ children }: { children: ReactNode }) { } return ( - + {children} ); @@ -69,4 +77,3 @@ export function useTheme() { } return context; } - diff --git a/mobile/src/lib/constants.ts b/mobile/src/lib/constants.ts index 3f4ab5d..92ff418 100644 --- a/mobile/src/lib/constants.ts +++ b/mobile/src/lib/constants.ts @@ -52,4 +52,3 @@ export const PRESET_LOCATIONS: Array<{ campus_section: 'West', }, ]; - diff --git a/mobile/src/lib/firebase/config.ts b/mobile/src/lib/firebase/config.ts index 6e4a421..32401ff 100644 --- a/mobile/src/lib/firebase/config.ts +++ b/mobile/src/lib/firebase/config.ts @@ -9,7 +9,11 @@ import { Firestore, } from 'firebase/firestore'; import { getAuth, connectAuthEmulator, type Auth } from 'firebase/auth'; -import { getStorage, connectStorageEmulator, type FirebaseStorage } from 'firebase/storage'; +import { + getStorage, + connectStorageEmulator, + type FirebaseStorage, +} from 'firebase/storage'; import Constants from 'expo-constants'; type Extra = { @@ -18,7 +22,8 @@ type Extra = { }; const extra = (Constants.expoConfig?.extra ?? {}) as Extra; -if (!extra.firebase) throw new Error('Missing firebase config in app.config.ts extra'); +if (!extra.firebase) + throw new Error('Missing firebase config in app.config.ts extra'); console.log( '🔥 Firebase init', @@ -27,7 +32,7 @@ console.log( isDevice: Device.isDevice, __DEV__, useEmulatorsFlag: extra.useEmulators === true, - }) + }), ); const app: FirebaseApp = getApps()[0] ?? initializeApp(extra.firebase); @@ -51,12 +56,14 @@ if (USE_EMULATORS) { try { connectFirestoreEmulator(firestore, HOST, 8080); connectAuthEmulator(auth, `http://${HOST}:9099`, { disableWarnings: true }); - connectStorageEmulator(storage, HOST, 9199); - console.log(`🧪 Emulators: Firestore http://${HOST}:8080, Auth http://${HOST}:9099, Storage http://${HOST}:9199`); + connectStorageEmulator(storage, HOST, 9199); + console.log( + `🧪 Emulators: Firestore http://${HOST}:8080, Auth http://${HOST}:9099, Storage http://${HOST}:9199`, + ); } catch (e) { console.warn('Emulator connection failed:', e); } } export const isUsingEmulator = () => USE_EMULATORS; -export default app; \ No newline at end of file +export default app; diff --git a/mobile/src/lib/firebase/events.ts b/mobile/src/lib/firebase/events.ts index 6f0dbbd..30f612f 100644 --- a/mobile/src/lib/firebase/events.ts +++ b/mobile/src/lib/firebase/events.ts @@ -246,7 +246,7 @@ export async function createEvent( batch.set( userRef, { uid: validated.creatorUid, events: [ref.id] }, - { merge: true } + { merge: true }, ); } @@ -319,4 +319,4 @@ export async function deleteEvent( } await batch.commit(); -} \ No newline at end of file +} diff --git a/mobile/src/lib/firebase/users.ts b/mobile/src/lib/firebase/users.ts index ddb0454..866f758 100644 --- a/mobile/src/lib/firebase/users.ts +++ b/mobile/src/lib/firebase/users.ts @@ -94,7 +94,7 @@ export async function getUserByEmail(email: string): Promise { export async function ensureUser(uid: string, seed: Partial = {}) { const validated = validateEnsureUser(uid, seed); - + const ref = userRef(uid); const snap = await getDoc(ref); if (!snap.exists()) { @@ -143,8 +143,15 @@ export async function removeEventFromUser(uid: string, eventId: string) { await batch.commit(); } -export async function setPushToken(uid: string, token: string, platform?: string) { - await updateDoc(userRef(uid), stripUndef({ pushToken: token, devicePlatform: platform })); +export async function setPushToken( + uid: string, + token: string, + platform?: string, +) { + await updateDoc( + userRef(uid), + stripUndef({ pushToken: token, devicePlatform: platform }), + ); } export async function setNotificationsEnabled(uid: string, enabled: boolean) { @@ -156,13 +163,14 @@ export async function getPushTokensByRole(role: Role): Promise { const qy = query( usersCol, where('role', '==', role), - where('notificationsEnabled', '==', true) + where('notificationsEnabled', '==', true), ); const snap = await getDocs(qy); const tokens: string[] = []; snap.forEach((d) => { const t = (d.data() as any)?.pushToken; - if (typeof t === 'string' && t.startsWith('ExponentPushToken')) tokens.push(t); + if (typeof t === 'string' && t.startsWith('ExponentPushToken')) + tokens.push(t); }); return tokens; } catch { @@ -173,7 +181,8 @@ export async function getPushTokensByRole(role: Role): Promise { const data = d.data() as any; if (data?.notificationsEnabled === false) return; const t = data?.pushToken; - if (typeof t === 'string' && t.startsWith('ExponentPushToken')) tokens.push(t); + if (typeof t === 'string' && t.startsWith('ExponentPushToken')) + tokens.push(t); }); return tokens; } diff --git a/mobile/src/lib/notifications.ts b/mobile/src/lib/notifications.ts index c0e5c6f..0a6b994 100644 --- a/mobile/src/lib/notifications.ts +++ b/mobile/src/lib/notifications.ts @@ -40,7 +40,10 @@ if (Platform.OS === 'android') { } /** Ask perms, get Expo token, save it */ -export async function registerForPushNotificationsAsync(uid: string, email?: string) { +export async function registerForPushNotificationsAsync( + uid: string, + email?: string, +) { if (!Device.isDevice) { console.log('🏗️ Push requires a physical device.'); return null; @@ -63,7 +66,8 @@ export async function registerForPushNotificationsAsync(uid: string, email?: str (Constants.expoConfig as any)?.extra?.eas?.projectId || process.env.EXPO_PUBLIC_EAS_PROJECT_ID || ''; - if (!projectId) throw new Error('Missing Expo projectId in extra.eas.projectId'); + if (!projectId) + throw new Error('Missing Expo projectId in extra.eas.projectId'); const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data; await setPushToken(uid, token, Platform.OS); @@ -75,7 +79,7 @@ export async function sendPush( tokens: string[], title: string, body: string, - data?: Record + data?: Record, ) { if (!tokens.length) return; const endpoint = 'https://exp.host/--/api/v2/push/send'; @@ -108,11 +112,19 @@ export async function sendPush( } /** Role helpers */ -export async function notifyAdmins(title: string, body: string, data?: Record) { +export async function notifyAdmins( + title: string, + body: string, + data?: Record, +) { const tokens = await getPushTokensByRole('Admin'); await sendPush(tokens, title, body, data); } -export async function notifyStudents(title: string, body: string, data?: Record) { +export async function notifyStudents( + title: string, + body: string, + data?: Record, +) { const tokens = await getPushTokensByRole('User'); await sendPush(tokens, title, body, data); } diff --git a/mobile/src/lib/rbacClient.tsx b/mobile/src/lib/rbacClient.tsx index 172afd3..9c91fda 100644 --- a/mobile/src/lib/rbacClient.tsx +++ b/mobile/src/lib/rbacClient.tsx @@ -26,7 +26,7 @@ const API_BASE_URL = process.env.EXPO_PUBLIC_BACKEND_URL; if (!API_BASE_URL) { console.warn( - '[rbacClient] EXPO_PUBLIC_BACKEND_URL is not set. RBAC calls will fail.' + '[rbacClient] EXPO_PUBLIC_BACKEND_URL is not set. RBAC calls will fail.', ); } @@ -36,7 +36,7 @@ if (!API_BASE_URL) { async function backendFetch( path: string, getToken: () => Promise, - init: RequestInit = {} + init: RequestInit = {}, ): Promise { if (!API_BASE_URL) { throw new Error('EXPO_PUBLIC_BACKEND_URL is not set'); @@ -90,13 +90,11 @@ export async function fetchMe(getToken: () => Promise) { * Allows a student to request staff access. * Returns { ok: true, status: "pending" } */ -export async function requestStaffRole( - getToken: () => Promise -) { +export async function requestStaffRole(getToken: () => Promise) { return backendFetch<{ ok: boolean; status: Status }>( '/api/request-role', getToken, - { method: 'POST' } + { method: 'POST' }, ); } @@ -105,25 +103,17 @@ export async function requestStaffRole( * Returns { pending: RbacUser[] } */ export async function fetchPendingStaff( - getToken: () => Promise + getToken: () => Promise, ) { - return backendFetch<{ pending: RbacUser[] }>( - '/api/admin/pending', - getToken - ); + return backendFetch<{ pending: RbacUser[] }>('/api/admin/pending', getToken); } /** * GET /api/admin/staff * Returns { staff: RbacUser[] } (active staff) */ -export async function fetchActiveStaff( - getToken: () => Promise -) { - return backendFetch<{ staff: RbacUser[] }>( - '/api/admin/staff', - getToken - ); +export async function fetchActiveStaff(getToken: () => Promise) { + return backendFetch<{ staff: RbacUser[] }>('/api/admin/staff', getToken); } /** @@ -132,13 +122,13 @@ export async function fetchActiveStaff( */ export async function approveStaff( uid: string, - getToken: () => Promise + getToken: () => Promise, ) { if (!uid) throw new Error('Missing user id'); return backendFetch<{ ok: boolean; userId: string }>( `/api/admin/approve/${uid}`, getToken, - { method: 'POST' } + { method: 'POST' }, ); } @@ -149,13 +139,13 @@ export async function approveStaff( */ export async function revokeStaff( uid: string, - getToken: () => Promise + getToken: () => Promise, ) { if (!uid) throw new Error('Missing user id'); return backendFetch<{ ok: boolean; userId: string }>( `/api/admin/revoke/${uid}`, getToken, - { method: 'POST' } + { method: 'POST' }, ); } @@ -180,7 +170,7 @@ function notifyListeners() { async function fetchRbacInternal( getToken: () => Promise, - force = false + force = false, ): Promise { // Reuse in-flight request if not forcing if (inFlightPromise && !force) { diff --git a/mobile/src/lib/schemas/events.schema.ts b/mobile/src/lib/schemas/events.schema.ts index 890ba88..2763c0c 100644 --- a/mobile/src/lib/schemas/events.schema.ts +++ b/mobile/src/lib/schemas/events.schema.ts @@ -1,7 +1,7 @@ // src/lib/schemas/events.schema.ts import { z } from 'zod'; -// Location schema +// Location schema const LocationSchema = z.object({ name: z.string().min(1), address: z.string().min(1), @@ -33,7 +33,7 @@ export const EventSchema = z.object({ locationDetails: z.string(), notes: z.string(), duration: z.number().int().positive(), - foodArrived: z.any(), // TODO: Timestamp Validation + foodArrived: z.any(), // TODO: Timestamp Validation foodAvailable: z.any(), // TODO: Timestamp Validation foods: z.array(FoodItemSchema), images: z.array(z.string().url()), @@ -41,57 +41,69 @@ export const EventSchema = z.object({ }); // Schema for creating events -export const CreateEventSchema = z.object({ - host: z.string().min(1).default('Unknown'), - name: z.string().min(1).default('Untitled'), - status: EventStatusSchema.default('drafted'), - Location: LocationSchema.optional(), - locationDetails: z.string().default(''), - notes: z.string().default(''), - duration: z.number().int().positive().default(30), - foodArrived: z.any().optional(), - foodAvailable: z.any().optional(), - foods: z.array(FoodItemSchema).default([]), - images: z.array(z.string().url()).default([]), - creatorUid: z.string().optional(), -}).partial(); +export const CreateEventSchema = z + .object({ + host: z.string().min(1).default('Unknown'), + name: z.string().min(1).default('Untitled'), + status: EventStatusSchema.default('drafted'), + Location: LocationSchema.optional(), + locationDetails: z.string().default(''), + notes: z.string().default(''), + duration: z.number().int().positive().default(30), + foodArrived: z.any().optional(), + foodAvailable: z.any().optional(), + foods: z.array(FoodItemSchema).default([]), + images: z.array(z.string().url()).default([]), + creatorUid: z.string().optional(), + }) + .partial(); // Schema for updating events -export const UpdateEventSchema = z.object({ - host: z.string().min(1), - name: z.string().min(1), - status: EventStatusSchema, - Location: LocationSchema, - locationDetails: z.string(), - notes: z.string(), - duration: z.number().int().positive(), - foodArrived: z.any(), - foodAvailable: z.any(), - foods: z.array(FoodItemSchema), - images: z.array(z.string().url()), -}).partial(); +export const UpdateEventSchema = z + .object({ + host: z.string().min(1), + name: z.string().min(1), + status: EventStatusSchema, + Location: LocationSchema, + locationDetails: z.string(), + notes: z.string(), + duration: z.number().int().positive(), + foodArrived: z.any(), + foodAvailable: z.any(), + foods: z.array(FoodItemSchema), + images: z.array(z.string().url()), + }) + .partial(); // Pagination options schema -export const PaginationOptsSchema = z.object({ - pageSize: z.number().int().positive().default(20), - after: z.any().optional().nullable(), - order: z.enum(['foodAvailable', 'foodArrived', 'duration']).default('foodAvailable'), - direction: z.enum(['asc', 'desc']).default('desc'), -}).partial(); +export const PaginationOptsSchema = z + .object({ + pageSize: z.number().int().positive().default(20), + after: z.any().optional().nullable(), + order: z + .enum(['foodAvailable', 'foodArrived', 'duration']) + .default('foodAvailable'), + direction: z.enum(['asc', 'desc']).default('desc'), + }) + .partial(); // Open events pagination schema -export const OpenEventsPaginationSchema = z.object({ - pageSize: z.number().int().positive().default(20), - after: z.any().optional().nullable(), -}).partial(); +export const OpenEventsPaginationSchema = z + .object({ + pageSize: z.number().int().positive().default(20), + after: z.any().optional().nullable(), + }) + .partial(); // Event IDs array schema export const EventIdsSchema = z.array(z.string().min(1)); // Delete options schema -export const DeleteOptsSchema = z.object({ - ownerUid: z.string().optional(), -}).optional(); +export const DeleteOptsSchema = z + .object({ + ownerUid: z.string().optional(), + }) + .optional(); // Helper to validate and parse with defaults export function validateCreateEvent(data: unknown) { @@ -104,4 +116,4 @@ export function validateUpdateEvent(data: unknown) { export function validateEventIds(data: unknown) { return EventIdsSchema.parse(data); -} \ No newline at end of file +} diff --git a/mobile/src/lib/schemas/reviews.schema.ts b/mobile/src/lib/schemas/reviews.schema.ts index 18b31b1..a47484c 100644 --- a/mobile/src/lib/schemas/reviews.schema.ts +++ b/mobile/src/lib/schemas/reviews.schema.ts @@ -34,4 +34,4 @@ export function validateCreateReview(data: unknown) { export function validateFetchReviewsOpts(data: unknown) { return FetchReviewsOptsSchema.parse(data); -} \ No newline at end of file +} diff --git a/mobile/src/lib/schemas/users.schema.ts b/mobile/src/lib/schemas/users.schema.ts index 54c310a..ba8ee88 100644 --- a/mobile/src/lib/schemas/users.schema.ts +++ b/mobile/src/lib/schemas/users.schema.ts @@ -27,11 +27,13 @@ export const EnsureUserSchema = z.object({ }); // Schema for updating user preferences -export const UpdatePreferencesSchema = z.object({ - locPref: z.array(z.string()).optional(), - timePref: z.array(z.string()).optional(), - foodPref: z.array(z.string()).optional(), -}).partial(); +export const UpdatePreferencesSchema = z + .object({ + locPref: z.array(z.string()).optional(), + timePref: z.array(z.string()).optional(), + foodPref: z.array(z.string()).optional(), + }) + .partial(); // Schema for event/review operations export const UserEventOpSchema = z.object({ @@ -50,4 +52,4 @@ export function validateUpdatePreferences(data: unknown) { export function validateUserEventOp(uid: string, eventId: string) { return UserEventOpSchema.parse({ uid, eventId }); -} \ No newline at end of file +} diff --git a/mobile/src/lib/theme.ts b/mobile/src/lib/theme.ts index ff1a42f..4e68a84 100644 --- a/mobile/src/lib/theme.ts +++ b/mobile/src/lib/theme.ts @@ -96,7 +96,7 @@ export const borderRadius = { full: 9999, }; -// Elevation +// Elevation export const elevation = { sm: 1, md: 2, @@ -104,4 +104,4 @@ export const elevation = { }; const theme = { colors, status, typography, spacing, borderRadius, elevation }; -export default theme; \ No newline at end of file +export default theme; diff --git a/mobile/src/lib/time.ts b/mobile/src/lib/time.ts index 51e4358..090eec1 100644 --- a/mobile/src/lib/time.ts +++ b/mobile/src/lib/time.ts @@ -4,47 +4,53 @@ import { Timestamp } from 'firebase/firestore'; export function tsToMs(ts?: any): number | null { if (!ts) return null; - + try { // Handle Firestore Timestamp if (ts instanceof Timestamp) { return ts.toMillis(); } - + // Handle object with toDate method - if (typeof ts === 'object' && 'toDate' in ts && typeof ts.toDate === 'function') { + if ( + typeof ts === 'object' && + 'toDate' in ts && + typeof ts.toDate === 'function' + ) { const date = ts.toDate(); return date.getTime(); } - + // Handle object with seconds property (Firestore timestamp-like) if (typeof ts === 'object' && 'seconds' in ts) { return ts.seconds * 1000 + (ts.nanoseconds || 0) / 1000000; } - + // Handle number (already milliseconds) if (typeof ts === 'number') { return ts; } - + // Handle string or Date const date = ts instanceof Date ? ts : new Date(ts); const time = date.getTime(); return isNaN(time) ? null : time; } catch (error) { - return null; } } -export function getExpiryMs(event: Pick): number | null { +export function getExpiryMs( + event: Pick, +): number | null { const startMs = tsToMs(event.foodAvailable); if (!startMs) return null; - - const durationMin = typeof event.duration === 'number' - ? event.duration - : Number(event.duration ?? 30); - + + const durationMin = + typeof event.duration === 'number' + ? event.duration + : Number(event.duration ?? 30); + return startMs + durationMin * 60_000; } @@ -62,4 +68,4 @@ export function breakdown(ms: number) { const minutes = Math.floor((s % 3600) / 60); const seconds = s % 60; return { days, hours, minutes, seconds }; -} \ No newline at end of file +} diff --git a/mobile/src/lib/utils.ts b/mobile/src/lib/utils.ts index e507ae4..a5fd1cc 100644 --- a/mobile/src/lib/utils.ts +++ b/mobile/src/lib/utils.ts @@ -3,14 +3,13 @@ import { Timestamp } from 'firebase/firestore'; export function formatTimestamp(timestamp: any): string { if (!timestamp) return 'N/A'; - + try { - const date = timestamp instanceof Timestamp - ? timestamp.toDate() - : new Date(timestamp); - + const date = + timestamp instanceof Timestamp ? timestamp.toDate() : new Date(timestamp); + if (isNaN(date.getTime())) return 'Invalid date'; - + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', diff --git a/mobile/tests/summary.md b/mobile/tests/summary.md deleted file mode 100644 index b4b107a..0000000 --- a/mobile/tests/summary.md +++ /dev/null @@ -1,56 +0,0 @@ -# Firebase Test Suite Summary - -## User CRUD Tests (4 tests) - -| Test | Purpose | -| --------------------------- | ------------------------------------------------------------------ | -| **test_user_create** | Creates Firestore user document with required fields | -| **test_user_read** | Reads user document and verifies email and role | -| **test_user_preferences** | Updates location and food preferences, verifies array counts | -| **test_user_accept_terms** | Updates agreedToTerms flag and confirms change | - -## Event CRUD Tests (6 tests) - -| Test | Purpose | -| ----------------------------- | ------------------------------------------------------------------ | -| **test_event_create** | Creates open event with foods, links to user, verifies ID | -| **test_event_create_drafted** | Creates drafted event with future timestamp | -| **test_event_read** | Fetches single event and verifies name and host fields | -| **test_event_update** | Updates event name and notes, verifies changes persisted | -| **test_event_status_change** | Changes event status from open to closed | -| **test_list_open_events** | Queries only events with status='open', ordered by foodAvailable | -| **test_event_fetch_by_ids** | Uses IN query to fetch multiple events by document IDs | - -## Review CRUD Tests (3 tests) - -| Test | Purpose | -| ------------------------------- | ----------------------------------------------------------------------- | -| **test_review_create** | Creates review with contact sharing, updates reviewedBy and user.reviews | -| **test_review_without_contact** | Creates anonymous review (shareContact=false), verifies contact absence | -| **test_review_list** | Lists all reviews for an event, verifies count | - -## Edge Case Tests (3 tests) - -| Test | Purpose | -| ---------------------------- | --------------------------------------------------------------------- | -| **test_array_operations** | Verifies arrayUnion deduplication and removeAllFromArray deletion | -| **test_batch_atomicity** | Uses batch commit to update event and user atomically | -| **test_pagination** | Fetches events with limit=2 to test pagination logic | - ---- - -## Running the Tests - -```bash -# Start Firebase emulator -firebase emulators:start - -# In another terminal -chmod +x test_firebase.sh -./test_firebase.sh -``` - -## Exit Codes - -- **0**: All tests passed -- **1**: One or more tests failed (count shown in output) diff --git a/mobile/tsconfig.json b/mobile/tsconfig.json index d82a882..484dfe0 100644 --- a/mobile/tsconfig.json +++ b/mobile/tsconfig.json @@ -1,9 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "lib": [ - "ES2020" - ], + "lib": ["ES2020"], "module": "ESNext", "jsx": "react-jsx", "moduleResolution": "Bundler", @@ -13,20 +11,12 @@ "strict": true, "allowJs": false, "isolatedModules": true, - "types": [ - "react", - "react-native" - ], + "types": ["react", "react-native"], "baseUrl": ".", "paths": { - "@/*": [ - "./*" - ] + "@/*": ["./*"] } }, - "include": [ - "**/*.ts", - "**/*.tsx" - ], + "include": ["**/*.ts", "**/*.tsx"], "extends": "expo/tsconfig.base" } From 7872c2b17ca756afb4f0b4259aa7acb3a51d1582 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 08:24:15 -0500 Subject: [PATCH 02/15] chore: add gitattributes to enforce LF line endings across repo --- mobile/.gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 mobile/.gitattributes diff --git a/mobile/.gitattributes b/mobile/.gitattributes new file mode 100644 index 0000000..0053faf --- /dev/null +++ b/mobile/.gitattributes @@ -0,0 +1,6 @@ +# Enforce Unix-style line endings globally +* text=auto eol=lf + +# Treat TypeScript as text with LF +*.ts text eol=lf +*.tsx text eol=lf From 0320e145f267b7cfc9eac37f41e151d043bbb25a Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 09:19:11 -0500 Subject: [PATCH 03/15] fix: minor refactoring to fix ESLint errs --- mobile/app/(admin)/create/page.tsx | 2 +- mobile/app/(admin)/index.tsx | 1 - mobile/app/(auth)/sign-in.tsx | 2 +- mobile/app/(auth)/sign-up.tsx | 4 +- mobile/app/(onboarding)/onboarding.tsx | 6 +- .../app/(shared)/settings/edit-location.tsx | 14 +- mobile/app/(shared)/settings/edit-name.tsx | 14 +- mobile/src/components/EventCard.tsx | 268 +++++++++--------- mobile/src/components/EventEditorModal.tsx | 4 +- mobile/src/components/PushTokenRegistrar.tsx | 9 +- mobile/src/components/SettingsScreen.tsx | 14 +- mobile/src/hooks/useEvents.ts | 6 +- mobile/src/lib/constants.ts | 4 +- mobile/src/lib/time.ts | 2 +- 14 files changed, 168 insertions(+), 182 deletions(-) diff --git a/mobile/app/(admin)/create/page.tsx b/mobile/app/(admin)/create/page.tsx index 52520e1..179612f 100644 --- a/mobile/app/(admin)/create/page.tsx +++ b/mobile/app/(admin)/create/page.tsx @@ -50,7 +50,7 @@ export default function CreateEventScreen() { Alert.alert('Success', 'Event created successfully!'); setShowModal(false); - } catch (error) { + } catch { Alert.alert('Error', 'Failed to create event'); } }; diff --git a/mobile/app/(admin)/index.tsx b/mobile/app/(admin)/index.tsx index 8c0f966..0bef6a6 100644 --- a/mobile/app/(admin)/index.tsx +++ b/mobile/app/(admin)/index.tsx @@ -15,7 +15,6 @@ import { typography, spacing, borderRadius } from '../../src/lib/theme'; import { useAllEvents } from '../../src/hooks/useEvents'; import { EventCard } from '../../src/components/EventCard'; import { EventEditorModal } from '../../src/components/EventEditorModal'; -import { router } from 'expo-router'; import type { Event } from '../../src/types'; import { updateEvent } from '../../src/lib/firebase/events'; import { notifyStudents } from '../../src/lib/notifications'; diff --git a/mobile/app/(auth)/sign-in.tsx b/mobile/app/(auth)/sign-in.tsx index a29a60e..aac8c2a 100644 --- a/mobile/app/(auth)/sign-in.tsx +++ b/mobile/app/(auth)/sign-in.tsx @@ -129,7 +129,7 @@ export default function SignInScreen() { - Don't have an account? Sign up + {"Don't have an account? Sign up"} diff --git a/mobile/app/(auth)/sign-up.tsx b/mobile/app/(auth)/sign-up.tsx index c6c60ad..6d8ff4f 100644 --- a/mobile/app/(auth)/sign-up.tsx +++ b/mobile/app/(auth)/sign-up.tsx @@ -78,7 +78,7 @@ export default function SignUpScreen() { } else { slideAnim.setValue(0); } - }, [pendingVerification]); + }, [pendingVerification, slideAnim]); const onSignUp = async (data: SignUpFields) => { if (!isLoaded) return; @@ -227,7 +227,7 @@ export default function SignUpScreen() { {/* Resend Code Link */} - Didn't receive a code?{' '} + {"Didn't receive a code? "} Resend diff --git a/mobile/app/(onboarding)/onboarding.tsx b/mobile/app/(onboarding)/onboarding.tsx index 12e5dec..c291151 100644 --- a/mobile/app/(onboarding)/onboarding.tsx +++ b/mobile/app/(onboarding)/onboarding.tsx @@ -141,10 +141,10 @@ export default function OnboardingScreen() { 👋 Welcome to BU Catering - Let's get to know you + Let's get to know you - What's your name? + What's your name? 🎓 How are you using FreeBites? - Choose whether you're signing up as a student or as staff. + Choose whether you're signing up as a student or as staff. diff --git a/mobile/app/(shared)/settings/edit-location.tsx b/mobile/app/(shared)/settings/edit-location.tsx index 44f6888..c56eeb1 100644 --- a/mobile/app/(shared)/settings/edit-location.tsx +++ b/mobile/app/(shared)/settings/edit-location.tsx @@ -22,14 +22,10 @@ export default function EditLocationScreen() { const { user } = useUser(); const { colors } = useTheme(); const [selectedLocations, setSelectedLocations] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); - useEffect(() => { - loadCurrentLocations(); - }, [user]); - - const loadCurrentLocations = async () => { + const loadCurrentLocations = useCallback(async () => { if (!user) return; try { @@ -45,7 +41,11 @@ export default function EditLocationScreen() { } finally { setIsLoading(false); } - }; + }, [user]); + + useEffect(() => { + loadCurrentLocations(); + }, [loadCurrentLocations]); const toggleLocation = (location: string) => { setSelectedLocations((prev) => diff --git a/mobile/app/(shared)/settings/edit-name.tsx b/mobile/app/(shared)/settings/edit-name.tsx index 96dba04..7ff5a6a 100644 --- a/mobile/app/(shared)/settings/edit-name.tsx +++ b/mobile/app/(shared)/settings/edit-name.tsx @@ -22,14 +22,10 @@ export default function EditNameScreen() { const { user } = useUser(); const { colors } = useTheme(); const [name, setName] = useState(''); - const [isLoading, setIsLoading] = useState(true); + const [, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); - useEffect(() => { - loadCurrentName(); - }, [user]); - - const loadCurrentName = async () => { + const loadCurrentName = useCallback(async () => { if (!user) return; try { @@ -45,7 +41,11 @@ export default function EditNameScreen() { } finally { setIsLoading(false); } - }; + }, [user]); + + useEffect(() => { + loadCurrentName(); + }, [loadCurrentName]); const handleSave = async () => { if (!user || !name.trim()) { diff --git a/mobile/src/components/EventCard.tsx b/mobile/src/components/EventCard.tsx index a12df44..a095f72 100644 --- a/mobile/src/components/EventCard.tsx +++ b/mobile/src/components/EventCard.tsx @@ -1,7 +1,6 @@ // src/components/EventCard.tsx import React, { useState } from 'react'; -import { View, Image, StyleSheet, Pressable } from 'react-native'; -import { Text } from 'react-native'; +import { View, Image, StyleSheet, Pressable, Text } from 'react-native'; import { useTheme } from '../lib/ThemeProvider'; import { typography, spacing, borderRadius } from '../lib/theme'; import { formatTimestamp } from '../lib/utils'; @@ -52,14 +51,7 @@ export function EventCard({ const totalDurationMs = (event.duration ?? 30) * 60 * 1000; if (totalDurationMs <= 0) return 0; return Math.max(0, Math.min(1, remainingMs / totalDurationMs)); - }, [ - shouldShowCountdown, - remainingMs, - expiryMs, - startMs, - event.duration, - event.name, - ]); + }, [shouldShowCountdown, remainingMs, expiryMs, startMs, event.duration]); // Auto-close event when timer expires (admin only) React.useEffect(() => { @@ -79,136 +71,132 @@ export function EventCard({ // Don't show expired open events to students if (isElapsed && !isAdmin && event.status === 'open') return null; - const styles = React.useMemo( - () => - StyleSheet.create({ - card: { - backgroundColor: colors.surface, - borderRadius: borderRadius.md, - marginBottom: spacing.md, - overflow: 'hidden', - borderWidth: 1, - borderColor: colors.border.light, - }, - image: { - width: '100%', - height: 200, - resizeMode: 'cover', - }, - content: { - padding: spacing.lg, - }, - title: { - ...typography.h5, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - subtitle: { - ...typography.bodySmall, - color: colors.text.secondary, - marginBottom: spacing.sm, - }, - countdownContainer: { - marginBottom: spacing.sm, - }, - countdownText: { - ...typography.bodySmall, - color: colors.error, - fontWeight: '600', - marginBottom: spacing.xs, - }, - progressBarBg: { - height: 8, - backgroundColor: colors.border.light, - borderRadius: borderRadius.sm, - overflow: 'hidden', - }, - progressBarFill: { - height: '100%', - borderRadius: borderRadius.sm, - }, - foodPreview: { - marginTop: spacing.sm, - }, - foodItem: { - ...typography.body, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - moreItems: { - ...typography.bodySmall, - color: colors.text.secondary, - fontStyle: 'italic', - }, - expandedContent: { - marginTop: spacing.md, - }, - divider: { - height: 1, - backgroundColor: colors.border.light, - marginBottom: spacing.md, - }, - infoRow: { - marginBottom: spacing.sm, - }, - infoLabel: { - ...typography.bodySmall, - color: colors.text.secondary, - fontWeight: '600', - }, - infoValue: { - ...typography.body, - color: colors.text.primary, - marginTop: spacing.xs / 2, - }, - foodList: { - marginTop: spacing.md, - }, - sectionLabel: { - ...typography.body, - color: colors.text.secondary, - fontWeight: '600', - marginBottom: spacing.xs, - }, - foodDetailItem: { - ...typography.body, - color: colors.text.primary, - marginBottom: spacing.xs, - }, - editButton: { - marginTop: spacing.lg, - backgroundColor: colors.primary, - paddingVertical: spacing.md, - paddingHorizontal: spacing.lg, - borderRadius: borderRadius.sm, - alignItems: 'center', - }, - editButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - expandIndicator: { - ...typography.caption, - color: colors.text.secondary, - textAlign: 'center', - marginTop: spacing.md, - }, - reviewButton: { - marginTop: spacing.md, - backgroundColor: colors.primary, - paddingVertical: spacing.md, - borderRadius: borderRadius.sm, - alignItems: 'center', - }, - reviewButtonText: { - ...typography.body, - color: colors.text.onPrimary, - fontWeight: '600', - }, - }), - [colors], - ); + const styles = StyleSheet.create({ + card: { + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + marginBottom: spacing.md, + overflow: 'hidden', + borderWidth: 1, + borderColor: colors.border.light, + }, + image: { + width: '100%', + height: 200, + resizeMode: 'cover', + }, + content: { + padding: spacing.lg, + }, + title: { + ...typography.h5, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + subtitle: { + ...typography.bodySmall, + color: colors.text.secondary, + marginBottom: spacing.sm, + }, + countdownContainer: { + marginBottom: spacing.sm, + }, + countdownText: { + ...typography.bodySmall, + color: colors.error, + fontWeight: '600', + marginBottom: spacing.xs, + }, + progressBarBg: { + height: 8, + backgroundColor: colors.border.light, + borderRadius: borderRadius.sm, + overflow: 'hidden', + }, + progressBarFill: { + height: '100%', + borderRadius: borderRadius.sm, + }, + foodPreview: { + marginTop: spacing.sm, + }, + foodItem: { + ...typography.body, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + moreItems: { + ...typography.bodySmall, + color: colors.text.secondary, + fontStyle: 'italic', + }, + expandedContent: { + marginTop: spacing.md, + }, + divider: { + height: 1, + backgroundColor: colors.border.light, + marginBottom: spacing.md, + }, + infoRow: { + marginBottom: spacing.sm, + }, + infoLabel: { + ...typography.bodySmall, + color: colors.text.secondary, + fontWeight: '600', + }, + infoValue: { + ...typography.body, + color: colors.text.primary, + marginTop: spacing.xs / 2, + }, + foodList: { + marginTop: spacing.md, + }, + sectionLabel: { + ...typography.body, + color: colors.text.secondary, + fontWeight: '600', + marginBottom: spacing.xs, + }, + foodDetailItem: { + ...typography.body, + color: colors.text.primary, + marginBottom: spacing.xs, + }, + editButton: { + marginTop: spacing.lg, + backgroundColor: colors.primary, + paddingVertical: spacing.md, + paddingHorizontal: spacing.lg, + borderRadius: borderRadius.sm, + alignItems: 'center', + }, + editButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + expandIndicator: { + ...typography.caption, + color: colors.text.secondary, + textAlign: 'center', + marginTop: spacing.md, + }, + reviewButton: { + marginTop: spacing.md, + backgroundColor: colors.primary, + paddingVertical: spacing.md, + borderRadius: borderRadius.sm, + alignItems: 'center', + }, + reviewButtonText: { + ...typography.body, + color: colors.text.onPrimary, + fontWeight: '600', + }, + }); return ( diff --git a/mobile/src/components/EventEditorModal.tsx b/mobile/src/components/EventEditorModal.tsx index ecf0271..c5a8f8c 100644 --- a/mobile/src/components/EventEditorModal.tsx +++ b/mobile/src/components/EventEditorModal.tsx @@ -1,6 +1,6 @@ // src/components/EventEditorModal.tsx import * as ImagePicker from 'expo-image-picker'; -import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage'; +import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; import React, { useState, useEffect } from 'react'; import { storage } from '../lib/firebase/config'; import { @@ -203,7 +203,7 @@ export function EventEditorModal({ } onClose(); - } catch (error) { + } catch { Alert.alert('Error', 'Failed to save event'); } finally { setSaving(false); diff --git a/mobile/src/components/PushTokenRegistrar.tsx b/mobile/src/components/PushTokenRegistrar.tsx index 2369c98..5d9595b 100644 --- a/mobile/src/components/PushTokenRegistrar.tsx +++ b/mobile/src/components/PushTokenRegistrar.tsx @@ -7,13 +7,12 @@ export default function PushTokenRegistrar() { const { isSignedIn } = useAuth(); const { user, isLoaded } = useUser(); + const primaryEmail = user?.primaryEmailAddress?.emailAddress ?? undefined; + useEffect(() => { if (!isSignedIn || !isLoaded || !user?.id) return; - registerForPushNotificationsAsync( - user.id, - user.primaryEmailAddress?.emailAddress ?? undefined, - ).catch(() => {}); - }, [isSignedIn, isLoaded, user?.id]); + registerForPushNotificationsAsync(user.id, primaryEmail).catch(() => {}); + }, [isSignedIn, isLoaded, user?.id, primaryEmail]); return null; } diff --git a/mobile/src/components/SettingsScreen.tsx b/mobile/src/components/SettingsScreen.tsx index ad5fa06..e4c3f15 100644 --- a/mobile/src/components/SettingsScreen.tsx +++ b/mobile/src/components/SettingsScreen.tsx @@ -37,7 +37,7 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const [name, setName] = useState(''); const [selectedLocations, setSelectedLocations] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [, setIsLoading] = useState(true); // Admin-only RBAC state const [pendingStaff, setPendingStaff] = useState([]); @@ -47,11 +47,7 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { const [staffError, setStaffError] = useState(null); const hasLoadedAdminLists = useRef(false); - useEffect(() => { - loadUserPreferences(); - }, [user]); - - const loadUserPreferences = async () => { + const loadUserPreferences = useCallback(async () => { if (!user) return; try { @@ -68,7 +64,11 @@ export default function SettingsScreen({ role }: SettingsScreenProps) { } finally { setIsLoading(false); } - }; + }, [user]); + + useEffect(() => { + loadUserPreferences(); + }, [loadUserPreferences]); const loadAdminLists = useCallback(async () => { if (role !== 'admin') return; diff --git a/mobile/src/hooks/useEvents.ts b/mobile/src/hooks/useEvents.ts index 900a1c1..e8c0d1a 100644 --- a/mobile/src/hooks/useEvents.ts +++ b/mobile/src/hooks/useEvents.ts @@ -29,7 +29,7 @@ export function useOpenEvents() { setEvents(sorted); setError(null); - } catch (err) { + } catch { setError('Failed to load events'); } finally { setLoading(false); @@ -62,7 +62,7 @@ export function useAllEvents() { }); setEvents(fetchedEvents); setError(null); - } catch (err) { + } catch { setError('Failed to load events'); } finally { setLoading(false); @@ -95,7 +95,7 @@ function getEventExpiryTime(event: Event): number { const durationMs = (event.duration ?? 30) * 60 * 1000; return startMs + durationMs; - } catch (error) { + } catch { return Date.now() + 30 * 60 * 1000; // Default to 30 minutes from now } } diff --git a/mobile/src/lib/constants.ts b/mobile/src/lib/constants.ts index 92ff418..081406b 100644 --- a/mobile/src/lib/constants.ts +++ b/mobile/src/lib/constants.ts @@ -1,12 +1,12 @@ // src/lib/constants.ts // Shared constants used across the app -export const PRESET_LOCATIONS: Array<{ +export const PRESET_LOCATIONS: { label: string; name: string; address: string; campus_section: string; -}> = [ +}[] = [ // Verified: BU School of Law, 765 Commonwealth Ave, 02215 { label: 'BU School of Law (LAW Tower)', diff --git a/mobile/src/lib/time.ts b/mobile/src/lib/time.ts index 090eec1..15e863a 100644 --- a/mobile/src/lib/time.ts +++ b/mobile/src/lib/time.ts @@ -35,7 +35,7 @@ export function tsToMs(ts?: any): number | null { const date = ts instanceof Date ? ts : new Date(ts); const time = date.getTime(); return isNaN(time) ? null : time; - } catch (error) { + } catch { return null; } } From bfebb239f9ef6af3751f344dfdb7011f4d83c7f6 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 09:28:04 -0500 Subject: [PATCH 04/15] ci: add workflow to run Expo lint on push and PR --- .github/workflows/mobile-lint.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/mobile-lint.yml diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml new file mode 100644 index 0000000..8994f35 --- /dev/null +++ b/.github/workflows/mobile-lint.yml @@ -0,0 +1,30 @@ +name: Mobile Lint + +on: + push: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: mobile + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + cache-dependency-path: mobile/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run Expo lint + run: npm run lint From f2ae5c051b81c37c3cad1955d447b3fbc8473b92 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 19:39:02 -0500 Subject: [PATCH 05/15] test: replace curl smoketest with jest e2e test --- backend/__tests__/rbac.test.ts | 299 ++ backend/jest.config.js | 22 + backend/package-lock.json | 6926 ++++++++++++++++++++++++-------- backend/package.json | 10 +- backend/test_rbac.sh | 183 - 5 files changed, 5478 insertions(+), 1962 deletions(-) create mode 100644 backend/__tests__/rbac.test.ts create mode 100644 backend/jest.config.js delete mode 100644 backend/test_rbac.sh diff --git a/backend/__tests__/rbac.test.ts b/backend/__tests__/rbac.test.ts new file mode 100644 index 0000000..c5be196 --- /dev/null +++ b/backend/__tests__/rbac.test.ts @@ -0,0 +1,299 @@ +import 'dotenv/config'; +import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; + +// Env Variables +const BASE_URL = process.env.BASE; +const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY!; +const STUDENT_ID = process.env.STUDENT_ID!; +const ADMIN_ID = process.env.ADMIN_ID!; + +let studentToken: string; +let adminToken: string; + +/** + * Request to Clerk API for + * - Modifying user metadata (roles, status) + * - Creating sessions and generating JWT tokens + */ +async function clerkApi(method: string, path: string, body?: any) { + const res = await fetch(`https://api.clerk.com${path}`, { + method, + headers: { + 'Authorization': `Bearer ${CLERK_SECRET_KEY}`, + 'Content-Type': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + + return res.json(); +} + +/** + * Create a Clerk session for a given user + * Then request a JWT token for that session + */ +async function makeToken(userId: string): Promise { + const session = await clerkApi('POST', '/v1/sessions', { user_id: userId }); + const token = await clerkApi('POST', `/v1/sessions/${session.id}/tokens`); + return token.jwt; +} + +/** + * Reset public metadata to a given role + status + * This modifies Clerk user state used for RBAC + */ +async function resetUser(userId: string, role: string, status: string) { + await clerkApi('PATCH', `/v1/users/${userId}`, { + public_metadata: { role, status } + }); +} + +/** + * Before all tests: + * - Reset both users to baseline in Clerk: + * - Admin -> admin / active + * - Student -> student / active + * - Generate new JWT tokens for both + */ +beforeAll(async () => { + await resetUser(ADMIN_ID, 'admin', 'active'); + await resetUser(STUDENT_ID, 'student', 'active'); + + studentToken = await makeToken(STUDENT_ID); + adminToken = await makeToken(ADMIN_ID); + + // Wait for Clerk + await new Promise(resolve => setTimeout(resolve, 1000)); +}, 30000); + +/** + * After all tests: + * - Reset student back to normal state + */ +afterAll(async () => { + await resetUser(STUDENT_ID, 'student', 'active'); +}, 15000); + + +// RBAC API Test Suites +describe('RBAC API Tests', () => { + + // Health Check + describe('GET /api/ping', () => { + it('should return ok', async () => { + const res = await fetch(`${BASE_URL}/api/ping`); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.ok).toBe(true); + }); + }); + + // Authentication & Identity + describe('GET /api/me', () => { + + it('should return 401 without auth', async () => { + const res = await fetch(`${BASE_URL}/api/me`); + expect(res.status).toBe(401); + }); + + it('should return RBAC info for student', async () => { + const res = await fetch(`${BASE_URL}/api/me`, { + headers: { Authorization: `Bearer ${studentToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.userId).toBe(STUDENT_ID); + expect(data.rbac.role).toBe('student'); + expect(data.rbac.status).toBe('active'); + }); + + it('should return RBAC info for admin', async () => { + const res = await fetch(`${BASE_URL}/api/me`, { + headers: { Authorization: `Bearer ${adminToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.userId).toBe(ADMIN_ID); + expect(data.rbac.role).toBe('admin'); + expect(data.rbac.status).toBe('active'); + }); + }); + + // Student Role-Request + describe('POST /api/request-role', () => { + + it('should allow student to request staff role', async () => { + const res = await fetch(`${BASE_URL}/api/request-role`, { + method: 'POST', + headers: { Authorization: `Bearer ${studentToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.ok).toBe(true); + expect(data.status).toBe('pending'); + }); + + it('should return 409 if already pending', async () => { + const res = await fetch(`${BASE_URL}/api/request-role`, { + method: 'POST', + headers: { Authorization: `Bearer ${studentToken}` } + }); + + expect(res.status).toBe(409); + }); + + it('should return 401 without auth', async () => { + const res = await fetch(`${BASE_URL}/api/request-role`, { + method: 'POST' + }); + + expect(res.status).toBe(401); + }); + }); + + // Pending Approvals List (Admin only) + describe('GET /api/admin/pending', () => { + + it('should return 403 for non-admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/pending`, { + headers: { Authorization: `Bearer ${studentToken}` } + }); + + expect(res.status).toBe(403); + }); + + it('should return pending users for admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/pending`, { + headers: { Authorization: `Bearer ${adminToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(Array.isArray(data.pending)).toBe(true); + + const studentInList = data.pending.find((u: any) => u.userId === STUDENT_ID); + expect(studentInList).toBeDefined(); + expect(studentInList.status).toBe('pending'); + }); + }); + + // Approval + describe('POST /api/admin/approve/:uid', () => { + + it('should return 403 for non-admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/approve/${STUDENT_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${studentToken}` } + }); + + expect(res.status).toBe(403); + }); + + it('should approve pending user', async () => { + const res = await fetch(`${BASE_URL}/api/admin/approve/${STUDENT_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${adminToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.ok).toBe(true); + expect(data.userId).toBe(STUDENT_ID); + + // Wait for Clerk before checking updated role + await new Promise(resolve => setTimeout(resolve, 1000)); + + const meRes = await fetch(`${BASE_URL}/api/me`, { + headers: { Authorization: `Bearer ${studentToken}` } + }); + const meData = await meRes.json(); + + expect(meData.rbac.role).toBe('staff'); + expect(meData.rbac.status).toBe('active'); + }); + + it('should return 400 if user is not pending anymore', async () => { + const res = await fetch(`${BASE_URL}/api/admin/approve/${STUDENT_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${adminToken}` } + }); + + expect(res.status).toBe(400); + }); + }); + + // Staff Listing (Admin only) + describe('GET /api/admin/staff', () => { + + it('should return 403 for non-admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/staff`, { + headers: { Authorization: `Bearer ${studentToken}` } + }); + + expect(res.status).toBe(403); + }); + + it('should return active staff for admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/staff`, { + headers: { Authorization: `Bearer ${adminToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(Array.isArray(data.staff)).toBe(true); + + const studentInList = data.staff.find((u: any) => u.userId === STUDENT_ID); + expect(studentInList).toBeDefined(); + expect(studentInList.role).toBe('staff'); + expect(studentInList.status).toBe('active'); + }); + }); + + // Revocation + describe('POST /api/admin/revoke/:uid', () => { + + it('should return 403 for non-admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/revoke/${STUDENT_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${studentToken}` } + }); + + expect(res.status).toBe(403); + }); + + it('should prevent self-revocation for admin', async () => { + const res = await fetch(`${BASE_URL}/api/admin/revoke/${ADMIN_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${adminToken}` } + }); + + expect(res.status).toBe(400); + }); + + it('should revoke staff privileges and restore student role', async () => { + const res = await fetch(`${BASE_URL}/api/admin/revoke/${STUDENT_ID}`, { + method: 'POST', + headers: { Authorization: `Bearer ${adminToken}` } + }); + const data = await res.json(); + + expect(res.status).toBe(200); + expect(data.ok).toBe(true); + + // Wait for Clerk before checking RBAC again + await new Promise(resolve => setTimeout(resolve, 1000)); + + const meRes = await fetch(`${BASE_URL}/api/me`, { + headers: { Authorization: `Bearer ${studentToken}` } + }); + const meData = await meRes.json(); + + expect(meData.rbac.role).toBe('student'); + expect(meData.rbac.status).toBe('active'); + }); + }); +}); diff --git a/backend/jest.config.js b/backend/jest.config.js new file mode 100644 index 0000000..9f44b3b --- /dev/null +++ b/backend/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.ts'], + collectCoverageFrom: [ + 'api/**/*.ts', + 'src/**/*.ts', + '!**/*.d.ts', + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + testTimeout: 30000, + verbose: true, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + tsconfig: { + esModuleInterop: true, + allowSyntheticDefaultImports: true, + } + }] + } +}; diff --git a/backend/package-lock.json b/backend/package-lock.json index 246710b..2880f08 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,11 +8,16 @@ "name": "rbac-api", "version": "1.0.0", "dependencies": { - "@clerk/backend": "^1.0.0", + "@clerk/backend": "^2.12.1", "@vercel/node": "^5.5.4" }, "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.11", "@types/node": "^20.0.0", + "dotenv": "^17.2.3", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", "typescript": "^5.0.0", "vercel": "^33.0.0" }, @@ -20,2177 +25,4749 @@ "node": "20.x" } }, - "node_modules/@clerk/backend": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.34.0.tgz", - "integrity": "sha512-9rZ8hQJVpX5KX2bEpiuVXfpjhojQCiqCWADJDdCI0PCeKxn58Ep0JPYiIcczg4VKUc3a7jve9vXylykG2XajLQ==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, "license": "MIT", "dependencies": { - "@clerk/shared": "^3.9.5", - "@clerk/types": "^4.59.3", - "cookie": "1.0.2", - "snakecase-keys": "8.0.1", - "tslib": "2.8.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=18.17.0" - }, - "peerDependencies": { - "svix": "^1.62.0" - }, - "peerDependenciesMeta": { - "svix": { - "optional": true - } + "node": ">=6.9.0" } }, - "node_modules/@clerk/shared": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.31.0.tgz", - "integrity": "sha512-Erw2waPbSWsU4SYpHg96LraeI5oDfKK6qNBykVGuwcncjkvVggcWWD7FkNfzjOiN+c/Emb8Wa1iGngbucX5fJw==", - "hasInstallScript": true, + "node_modules/@babel/code-frame/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, "license": "MIT", "dependencies": { - "csstype": "3.1.3", - "dequal": "2.0.3", - "glob-to-regexp": "0.4.1", - "js-cookie": "3.0.5", - "std-env": "^3.9.0", - "swr": "2.3.4" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=18.17.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@clerk/types": { - "version": "4.97.1", - "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.97.1.tgz", - "integrity": "sha512-rNWBdE36fUE+zhomCHs6WrO7XGZQPOFMT695GVGqgQas5iN1MJ4HgO5LRasNMXscOGuA0EX5tzRoPK6ZufdiZQ==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, "license": "MIT", "dependencies": { - "@clerk/shared": "^3.31.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=18.17.0" + "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@babel/generator/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==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@edge-runtime/format": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@edge-runtime/format/-/format-2.2.1.tgz", - "integrity": "sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==", - "license": "MPL-2.0", - "engines": { - "node": ">=16" + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/@edge-runtime/node-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/node-utils/-/node-utils-2.3.0.tgz", - "integrity": "sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==", - "license": "MPL-2.0", - "engines": { - "node": ">=16" + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@edge-runtime/ponyfill": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@edge-runtime/ponyfill/-/ponyfill-2.4.2.tgz", - "integrity": "sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==", - "license": "MPL-2.0", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=6.9.0" } }, - "node_modules/@edge-runtime/primitives": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-4.1.0.tgz", - "integrity": "sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==", - "license": "MPL-2.0", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, "engines": { - "node": ">=16" + "node": ">=6.9.0" } }, - "node_modules/@edge-runtime/vm": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.2.0.tgz", - "integrity": "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==", - "license": "MPL-2.0", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", "dependencies": { - "@edge-runtime/primitives": "4.1.0" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": ">=16" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=12" + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/fs-minipass/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "@babel/helper-plugin-utils": "^7.8.0" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "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==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "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==", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" + "ms": "^2.1.3" }, "engines": { - "node": ">= 8.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, "engines": { - "node": ">= 10" + "node": ">=6.9.0" } }, - "node_modules/@ts-morph/common": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.11.1.tgz", - "integrity": "sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@clerk/backend": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.22.0.tgz", + "integrity": "sha512-d/PWhQ9l531SJArX3vYXzAMPLYkkSmfVOl6/MxuiCWTUzx8J8ZIxXT/duXQNM6jM8NRfyQbnt+kLPoE+L0zBlg==", "license": "MIT", "dependencies": { - "fast-glob": "^3.2.7", - "minimatch": "^3.0.4", - "mkdirp": "^1.0.4", - "path-browserify": "^1.0.1" + "@clerk/shared": "^3.34.0", + "@clerk/types": "^4.100.0", + "cookie": "1.0.2", + "standardwebhooks": "^1.0.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" } }, - "node_modules/@ts-morph/common/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/@clerk/shared": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.34.0.tgz", + "integrity": "sha512-ZuruC+VtvejdnNj+e1sI5JK8tRdOc9wsMWyLNeumXrsZGh92DHTT+FGxCSDPJcmro0iIYw8ZiZqjHT9Fb2gMTg==", + "hasInstallScript": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "csstype": "3.1.3", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.9.0", + "swr": "2.3.4" }, "engines": { - "node": ">=10" + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", - "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "node_modules/@clerk/types": { + "version": "4.100.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.100.0.tgz", + "integrity": "sha512-QT9GIO3bDL4R+ojAtc8XeJc/ehkdcF/+6PN3tcRWwGe4eMbX2TQ54/mhXJiY01DhMwBnF5cgK3O/z3D+34B07Q==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@clerk/shared": "^3.34.0" + }, + "engines": { + "node": ">=18.17.0" } }, - "node_modules/@vercel/build-utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-7.11.0.tgz", - "integrity": "sha512-UFrx1hNIjNJJkd0NZrYfaOrmcWhQmrVsbKe9o3L9jX9J1iufG685wIZ9tFCKKC0Fa2HWbNDNzNxrE5SCAS2lyA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@vercel/error-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.2.tgz", - "integrity": "sha512-Sj0LFafGpYr6pfCqrQ82X6ukRl5qpmVrHM/191kNYFqkkB9YkjlMAj6QcEsvCG259x4QZ7Tya++0AB85NDPbKQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@vercel/fun": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vercel/fun/-/fun-1.1.0.tgz", - "integrity": "sha512-SpuPAo+MlAYMtcMcC0plx7Tv4Mp7SQhJJj1iIENlOnABL24kxHpL09XLQMGzZIzIW7upR8c3edwgfpRtp+dhVw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", "dependencies": { - "@tootallnate/once": "2.0.0", - "async-listen": "1.2.0", - "debug": "4.1.1", - "execa": "3.2.0", - "fs-extra": "8.1.0", - "generic-pool": "3.4.2", - "micro": "9.3.5-canary.3", - "ms": "2.1.1", - "node-fetch": "2.6.7", - "path-match": "1.2.4", - "promisepipe": "3.0.0", - "semver": "7.3.5", - "stat-mode": "0.3.0", - "stream-to-promise": "2.2.0", - "tar": "4.4.18", - "tree-kill": "1.2.2", - "uid-promise": "1.0.0", - "uuid": "3.3.2", - "xdg-app-paths": "5.1.0", - "yauzl-promise": "2.1.3" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">= 10" + "node": ">=12" } }, - "node_modules/@vercel/gatsby-plugin-vercel-analytics": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-analytics/-/gatsby-plugin-vercel-analytics-1.0.11.tgz", - "integrity": "sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "web-vitals": "0.2.4" + "node_modules/@edge-runtime/format": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@edge-runtime/format/-/format-2.2.1.tgz", + "integrity": "sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==", + "license": "MPL-2.0", + "engines": { + "node": ">=16" } }, - "node_modules/@vercel/gatsby-plugin-vercel-builder": { - "version": "2.0.24", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.24.tgz", - "integrity": "sha512-b02ifu8WCmz4ARjkC9AyuOxpXa0Tmh0uIbDDYvyvDRpvohQY53eC3sXKVOejnmQbi9KojkaJsQRvMTBRh9BUHA==", - "dev": true, + "node_modules/@edge-runtime/node-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/node-utils/-/node-utils-2.3.0.tgz", + "integrity": "sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==", + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/ponyfill": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@edge-runtime/ponyfill/-/ponyfill-2.4.2.tgz", + "integrity": "sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==", + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/primitives": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-4.1.0.tgz", + "integrity": "sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==", + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/vm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.2.0.tgz", + "integrity": "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==", + "license": "MPL-2.0", "dependencies": { - "@sinclair/typebox": "0.25.24", - "@vercel/build-utils": "7.11.0", - "@vercel/routing-utils": "3.1.0", - "esbuild": "0.14.47", - "etag": "1.8.1", - "fs-extra": "11.1.0" + "@edge-runtime/primitives": "4.1.0" + }, + "engines": { + "node": ">=16" } }, - "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", - "dev": true, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=12" } }, - "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@vercel/go": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@vercel/go/-/go-3.1.1.tgz", - "integrity": "sha512-mrzomNYltxkjvtUmaYry5YEyvwTz6c/QQHE5Gr/pPGRIniUiP6T6OFOJ49RBN7e6pRXaNzHPVuidiuBhvHh5+Q==", - "dev": true, - "license": "Apache-2.0" + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@vercel/hydrogen": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@vercel/hydrogen/-/hydrogen-1.0.2.tgz", - "integrity": "sha512-/Q2MKk1GfOuZAnkE9jQexjtUQqanbY65R+xtJWd9yKIgwcfRI1hxiNH3uXyVM5AvLoY+fxxULkSuxDtUKpkJpQ==", + "node_modules/@isaacs/fs-minipass/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@vercel/static-config": "3.0.0", - "ts-morph": "12.0.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@vercel/next": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.2.0.tgz", - "integrity": "sha512-2KSXdPHpfPWaf0tKTBxOWvdc8e9TPNARjmqtgYUsrl1TVaBNFsZ0GV0kWaVLEw4o7CWfREt8ZY064sNVb1BcAQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@vercel/nft": "0.26.4" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@vercel/nft": { - "version": "0.26.4", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.4.tgz", - "integrity": "sha512-j4jCOOXke2t8cHZCIxu1dzKLHLcFmYzC3yqAK6MfZznOL1QIJKd0xcFsXK3zcqzU7ScsE2zWkiMMNHGMHgp+FA==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.5", - "@rollup/pluginutils": "^4.0.0", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.2", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.2", - "node-gyp-build": "^4.2.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=16" - } - }, - "node_modules/@vercel/node": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.5.4.tgz", - "integrity": "sha512-JunVzycaByoSTi8PeCGLnllblRsn3O4V8D+P/S/NOw3et6eOhw761hGDAzIZURYlQYPifIioZKsUI30nOxFy8Q==", - "license": "Apache-2.0", - "dependencies": { - "@edge-runtime/node-utils": "2.3.0", - "@edge-runtime/primitives": "4.1.0", - "@edge-runtime/vm": "3.2.0", - "@types/node": "16.18.11", - "@vercel/build-utils": "12.2.4", - "@vercel/error-utils": "2.0.3", - "@vercel/nft": "0.30.1", - "@vercel/static-config": "3.1.2", - "async-listen": "3.0.0", - "cjs-module-lexer": "1.2.3", - "edge-runtime": "2.5.9", - "es-module-lexer": "1.4.1", - "esbuild": "0.14.47", - "etag": "1.8.1", - "mime-types": "2.1.35", - "node-fetch": "2.6.9", - "path-to-regexp": "6.1.0", - "path-to-regexp-updated": "npm:path-to-regexp@6.3.0", - "ts-morph": "12.0.0", - "ts-node": "10.9.1", - "typescript": "4.9.5", - "undici": "5.28.4" - } - }, - "node_modules/@vercel/node/node_modules/@mapbox/node-pre-gyp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", - "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", - "license": "BSD-3-Clause", - "dependencies": { - "consola": "^3.2.3", - "detect-libc": "^2.0.0", - "https-proxy-agent": "^7.0.5", - "node-fetch": "^2.6.7", - "nopt": "^8.0.0", - "semver": "^7.5.3", - "tar": "^7.4.0" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - }, - "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=14.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "rollup": { + "node-notifier": { "optional": true } } }, - "node_modules/@vercel/node/node_modules/@types/node": { - "version": "16.18.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", - "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==", - "license": "MIT" - }, - "node_modules/@vercel/node/node_modules/@vercel/build-utils": { - "version": "12.2.4", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-12.2.4.tgz", - "integrity": "sha512-/Dt94nn6+LRCnhNzCLM7W/1/Rc7N+VQME9ibplh203HBZU0FLSmdjSNrB2iyV5aZkSlPEnohmY4HniTm5vMPyA==", - "license": "Apache-2.0" - }, - "node_modules/@vercel/node/node_modules/@vercel/error-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.3.tgz", - "integrity": "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==", - "license": "Apache-2.0" - }, - "node_modules/@vercel/node/node_modules/@vercel/nft": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.30.1.tgz", - "integrity": "sha512-2mgJZv4AYBFkD/nJ4QmiX5Ymxi+AisPLPcS/KPXVqniyQNqKXX+wjieAbDXQP3HcogfEbpHoRMs49Cd4pfkk8g==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^2.0.0", - "@rollup/pluginutils": "^5.1.3", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.5", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^10.4.5", - "graceful-fs": "^4.2.9", - "node-gyp-build": "^4.2.2", - "picomatch": "^4.0.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/@vercel/static-config": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.2.tgz", - "integrity": "sha512-2d+TXr6K30w86a+WbMbGm2W91O0UzO5VeemZYBBUJbCjk/5FLLGIi8aV6RS2+WmaRvtcqNTn2pUA7nCOK3bGcQ==", - "license": "Apache-2.0", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ajv": "8.6.3", - "json-schema-to-ts": "1.6.4", - "ts-morph": "12.0.0" - } - }, - "node_modules/@vercel/node/node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "license": "ISC", + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/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==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@vercel/node/node_modules/async-listen": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", - "integrity": "sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==", - "license": "MIT", "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vercel/node/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@vercel/node/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==", + "node_modules/@jest/reporters/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==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/@jest/schemas/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, "license": "MIT" }, - "node_modules/@vercel/node/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node_modules/@jest/source-map/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@vercel/node/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">= 18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "license": "ISC", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/@jest/transform/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==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@vercel/node/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "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==", + "dev": true, + "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": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@vercel/node/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.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==", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@vercel/node/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" + "node_modules/@jridgewell/gen-mapping/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@vercel/node/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@vercel/python": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@vercel/python/-/python-4.1.1.tgz", - "integrity": "sha512-EbAdKOZ0hPd5b59tLt7R3RQK1azNvuZTrCFRAVHNjqcIHNCmrSvjag5zBGn7Memkk8qWb3+CgBw9K/3LJKei0w==", + "node_modules/@jridgewell/remapping/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==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@vercel/redwood": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.0.8.tgz", - "integrity": "sha512-hAu7SYXDt+W7kscjtQ5NsuNflXH+QB5/xAdA6FRSS/e41lG6Xq6pqLMDobqq4BR7E2PpppVDw2DUx9KzPNoeEw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-3-Clause", "dependencies": { - "@vercel/nft": "0.26.4", - "@vercel/routing-utils": "3.1.0", - "semver": "6.3.1" + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@vercel/redwood/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@mapbox/node-pre-gyp/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=10" } }, - "node_modules/@vercel/remix-builder": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-2.1.5.tgz", - "integrity": "sha512-VaDhsNg/1lZ7h6GJnaykActeZTRtFQz45qDNwKrHM+Nw5/ocwTun9sCJZY/ziECUNuQEJv95z3wUDhNweG+/9w==", + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@vercel/error-utils": "2.0.2", - "@vercel/nft": "0.26.4", - "@vercel/static-config": "3.0.0", - "ts-morph": "12.0.0" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@vercel/routing-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vercel/routing-utils/-/routing-utils-3.1.0.tgz", - "integrity": "sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==", + "node_modules/@mapbox/node-pre-gyp/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "path-to-regexp": "6.1.0" + "yallist": "^4.0.0" }, - "optionalDependencies": { - "ajv": "^6.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@vercel/ruby": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vercel/ruby/-/ruby-2.0.5.tgz", - "integrity": "sha512-Gfm8HDech41vf+EPleRzgoJUnDTJerKgckMm4KX0JT860gV9XBMSOWYH7eMWHmMza104+HRCWL7wT6OlpftF2Q==", + "node_modules/@mapbox/node-pre-gyp/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "license": "Apache-2.0" + "license": "ISC", + "engines": { + "node": ">=8" + } }, - "node_modules/@vercel/static-build": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.4.6.tgz", - "integrity": "sha512-LCmEBXRse7Bt46fo4OUzkq6RL1Q26oMWvmbFsW5uKi6bkT8asU1U5/zw9PQTeFQjGRL2vkUi22fGXF6XHuuqsA==", + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", - "@vercel/gatsby-plugin-vercel-builder": "2.0.24", - "@vercel/static-config": "3.0.0", - "ts-morph": "12.0.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@vercel/static-config": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.0.0.tgz", - "integrity": "sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==", + "node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "ajv": "8.6.3", - "json-schema-to-ts": "1.6.4", - "ts-morph": "12.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@vercel/static-config/node_modules/ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "bin": { + "mkdirp": "bin/cmd.js" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=10" } }, - "node_modules/@vercel/static-config/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/@mapbox/node-pre-gyp/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true, "license": "MIT" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "ISC" + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.11.1.tgz", + "integrity": "sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "minimatch": "^3.0.4", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/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/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/build-utils": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-7.11.0.tgz", + "integrity": "sha512-UFrx1hNIjNJJkd0NZrYfaOrmcWhQmrVsbKe9o3L9jX9J1iufG685wIZ9tFCKKC0Fa2HWbNDNzNxrE5SCAS2lyA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/error-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.2.tgz", + "integrity": "sha512-Sj0LFafGpYr6pfCqrQ82X6ukRl5qpmVrHM/191kNYFqkkB9YkjlMAj6QcEsvCG259x4QZ7Tya++0AB85NDPbKQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/fun": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vercel/fun/-/fun-1.1.0.tgz", + "integrity": "sha512-SpuPAo+MlAYMtcMcC0plx7Tv4Mp7SQhJJj1iIENlOnABL24kxHpL09XLQMGzZIzIW7upR8c3edwgfpRtp+dhVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@tootallnate/once": "2.0.0", + "async-listen": "1.2.0", + "debug": "4.1.1", + "execa": "3.2.0", + "fs-extra": "8.1.0", + "generic-pool": "3.4.2", + "micro": "9.3.5-canary.3", + "ms": "2.1.1", + "node-fetch": "2.6.7", + "path-match": "1.2.4", + "promisepipe": "3.0.0", + "semver": "7.3.5", + "stat-mode": "0.3.0", + "stream-to-promise": "2.2.0", + "tar": "4.4.18", + "tree-kill": "1.2.2", + "uid-promise": "1.0.0", + "uuid": "3.3.2", + "xdg-app-paths": "5.1.0", + "yauzl-promise": "2.1.3" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@vercel/gatsby-plugin-vercel-analytics": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-analytics/-/gatsby-plugin-vercel-analytics-1.0.11.tgz", + "integrity": "sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "web-vitals": "0.2.4" + } + }, + "node_modules/@vercel/gatsby-plugin-vercel-builder": { + "version": "2.0.24", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.24.tgz", + "integrity": "sha512-b02ifu8WCmz4ARjkC9AyuOxpXa0Tmh0uIbDDYvyvDRpvohQY53eC3sXKVOejnmQbi9KojkaJsQRvMTBRh9BUHA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "0.25.24", + "@vercel/build-utils": "7.11.0", + "@vercel/routing-utils": "3.1.0", + "esbuild": "0.14.47", + "etag": "1.8.1", + "fs-extra": "11.1.0" + } + }, + "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@vercel/gatsby-plugin-vercel-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@vercel/go": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vercel/go/-/go-3.1.1.tgz", + "integrity": "sha512-mrzomNYltxkjvtUmaYry5YEyvwTz6c/QQHE5Gr/pPGRIniUiP6T6OFOJ49RBN7e6pRXaNzHPVuidiuBhvHh5+Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/hydrogen": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vercel/hydrogen/-/hydrogen-1.0.2.tgz", + "integrity": "sha512-/Q2MKk1GfOuZAnkE9jQexjtUQqanbY65R+xtJWd9yKIgwcfRI1hxiNH3uXyVM5AvLoY+fxxULkSuxDtUKpkJpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@vercel/static-config": "3.0.0", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.2.0.tgz", + "integrity": "sha512-2KSXdPHpfPWaf0tKTBxOWvdc8e9TPNARjmqtgYUsrl1TVaBNFsZ0GV0kWaVLEw4o7CWfREt8ZY064sNVb1BcAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@vercel/nft": "0.26.4" + } + }, + "node_modules/@vercel/nft": { + "version": "0.26.4", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.4.tgz", + "integrity": "sha512-j4jCOOXke2t8cHZCIxu1dzKLHLcFmYzC3yqAK6MfZznOL1QIJKd0xcFsXK3zcqzU7ScsE2zWkiMMNHGMHgp+FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.5", + "@rollup/pluginutils": "^4.0.0", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.2", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.2", + "node-gyp-build": "^4.2.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vercel/node": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.5.4.tgz", + "integrity": "sha512-JunVzycaByoSTi8PeCGLnllblRsn3O4V8D+P/S/NOw3et6eOhw761hGDAzIZURYlQYPifIioZKsUI30nOxFy8Q==", + "license": "Apache-2.0", + "dependencies": { + "@edge-runtime/node-utils": "2.3.0", + "@edge-runtime/primitives": "4.1.0", + "@edge-runtime/vm": "3.2.0", + "@types/node": "16.18.11", + "@vercel/build-utils": "12.2.4", + "@vercel/error-utils": "2.0.3", + "@vercel/nft": "0.30.1", + "@vercel/static-config": "3.1.2", + "async-listen": "3.0.0", + "cjs-module-lexer": "1.2.3", + "edge-runtime": "2.5.9", + "es-module-lexer": "1.4.1", + "esbuild": "0.14.47", + "etag": "1.8.1", + "mime-types": "2.1.35", + "node-fetch": "2.6.9", + "path-to-regexp": "6.1.0", + "path-to-regexp-updated": "npm:path-to-regexp@6.3.0", + "ts-morph": "12.0.0", + "ts-node": "10.9.1", + "typescript": "4.9.5", + "undici": "5.28.4" + } + }, + "node_modules/@vercel/node/node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", + "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "license": "BSD-3-Clause", + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@vercel/node/node_modules/@types/node": { + "version": "16.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", + "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==", + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/@vercel/build-utils": { + "version": "12.2.4", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-12.2.4.tgz", + "integrity": "sha512-/Dt94nn6+LRCnhNzCLM7W/1/Rc7N+VQME9ibplh203HBZU0FLSmdjSNrB2iyV5aZkSlPEnohmY4HniTm5vMPyA==", + "license": "Apache-2.0" + }, + "node_modules/@vercel/node/node_modules/@vercel/error-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.3.tgz", + "integrity": "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==", + "license": "Apache-2.0" + }, + "node_modules/@vercel/node/node_modules/@vercel/nft": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.30.1.tgz", + "integrity": "sha512-2mgJZv4AYBFkD/nJ4QmiX5Ymxi+AisPLPcS/KPXVqniyQNqKXX+wjieAbDXQP3HcogfEbpHoRMs49Cd4pfkk8g==", + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^10.4.5", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/@vercel/static-config": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.2.tgz", + "integrity": "sha512-2d+TXr6K30w86a+WbMbGm2W91O0UzO5VeemZYBBUJbCjk/5FLLGIi8aV6RS2+WmaRvtcqNTn2pUA7nCOK3bGcQ==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "8.6.3", + "json-schema-to-ts": "1.6.4", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/node/node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@vercel/node/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/@vercel/node/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@vercel/node/node_modules/async-listen": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", + "integrity": "sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@vercel/node/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vercel/node/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vercel/node/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/@vercel/node/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vercel/node/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@vercel/node/node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@vercel/node/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@vercel/node/node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@vercel/node/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@vercel/node/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vercel/node/node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/node/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@vercel/node/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@vercel/python": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vercel/python/-/python-4.1.1.tgz", + "integrity": "sha512-EbAdKOZ0hPd5b59tLt7R3RQK1azNvuZTrCFRAVHNjqcIHNCmrSvjag5zBGn7Memkk8qWb3+CgBw9K/3LJKei0w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/redwood": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.0.8.tgz", + "integrity": "sha512-hAu7SYXDt+W7kscjtQ5NsuNflXH+QB5/xAdA6FRSS/e41lG6Xq6pqLMDobqq4BR7E2PpppVDw2DUx9KzPNoeEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@vercel/nft": "0.26.4", + "@vercel/routing-utils": "3.1.0", + "semver": "6.3.1" + } + }, + "node_modules/@vercel/redwood/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@vercel/remix-builder": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-2.1.5.tgz", + "integrity": "sha512-VaDhsNg/1lZ7h6GJnaykActeZTRtFQz45qDNwKrHM+Nw5/ocwTun9sCJZY/ziECUNuQEJv95z3wUDhNweG+/9w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@vercel/error-utils": "2.0.2", + "@vercel/nft": "0.26.4", + "@vercel/static-config": "3.0.0", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/routing-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vercel/routing-utils/-/routing-utils-3.1.0.tgz", + "integrity": "sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "path-to-regexp": "6.1.0" + }, + "optionalDependencies": { + "ajv": "^6.0.0" + } + }, + "node_modules/@vercel/ruby": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vercel/ruby/-/ruby-2.0.5.tgz", + "integrity": "sha512-Gfm8HDech41vf+EPleRzgoJUnDTJerKgckMm4KX0JT860gV9XBMSOWYH7eMWHmMza104+HRCWL7wT6OlpftF2Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/static-build": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.4.6.tgz", + "integrity": "sha512-LCmEBXRse7Bt46fo4OUzkq6RL1Q26oMWvmbFsW5uKi6bkT8asU1U5/zw9PQTeFQjGRL2vkUi22fGXF6XHuuqsA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", + "@vercel/gatsby-plugin-vercel-builder": "2.0.24", + "@vercel/static-config": "3.0.0", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/static-config": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.0.0.tgz", + "integrity": "sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "8.6.3", + "json-schema-to-ts": "1.6.4", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/static-config/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@vercel/static-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "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": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async-listen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-1.2.0.tgz", + "integrity": "sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", + "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001755", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz", + "integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-block-writer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", + "integrity": "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==", + "license": "MIT" + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "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-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-hrtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-3.0.0.tgz", + "integrity": "sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, + "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.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/edge-runtime": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/edge-runtime/-/edge-runtime-2.5.9.tgz", + "integrity": "sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==", + "license": "MPL-2.0", + "dependencies": { + "@edge-runtime/format": "2.2.1", + "@edge-runtime/ponyfill": "2.4.2", + "@edge-runtime/vm": "3.2.0", + "async-listen": "3.0.1", + "mri": "1.2.0", + "picocolors": "1.0.0", + "pretty-ms": "7.0.1", + "signal-exit": "4.0.2", + "time-span": "4.0.0" + }, + "bin": { + "edge-runtime": "dist/cli/index.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/edge-runtime/node_modules/async-listen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.1.tgz", + "integrity": "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.254", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz", + "integrity": "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.47.tgz", + "integrity": "sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "esbuild-android-64": "0.14.47", + "esbuild-android-arm64": "0.14.47", + "esbuild-darwin-64": "0.14.47", + "esbuild-darwin-arm64": "0.14.47", + "esbuild-freebsd-64": "0.14.47", + "esbuild-freebsd-arm64": "0.14.47", + "esbuild-linux-32": "0.14.47", + "esbuild-linux-64": "0.14.47", + "esbuild-linux-arm": "0.14.47", + "esbuild-linux-arm64": "0.14.47", + "esbuild-linux-mips64le": "0.14.47", + "esbuild-linux-ppc64le": "0.14.47", + "esbuild-linux-riscv64": "0.14.47", + "esbuild-linux-s390x": "0.14.47", + "esbuild-netbsd-64": "0.14.47", + "esbuild-openbsd-64": "0.14.47", + "esbuild-sunos-64": "0.14.47", + "esbuild-windows-32": "0.14.47", + "esbuild-windows-64": "0.14.47", + "esbuild-windows-arm64": "0.14.47" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz", + "integrity": "sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz", + "integrity": "sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz", + "integrity": "sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz", + "integrity": "sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz", + "integrity": "sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz", + "integrity": "sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz", + "integrity": "sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz", + "integrity": "sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz", + "integrity": "sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz", + "integrity": "sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz", + "integrity": "sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz", + "integrity": "sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz", + "integrity": "sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz", + "integrity": "sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz", + "integrity": "sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz", + "integrity": "sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/esbuild-sunos-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz", + "integrity": "sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/esbuild-windows-32": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz", + "integrity": "sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "peerDependencies": { - "acorn": "^8" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "node_modules/esbuild-windows-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz", + "integrity": "sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz", + "integrity": "sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "debug": "4" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 6.0.0" + "node": ">=12" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6" } }, - "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==", + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "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": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "node_modules/events-intercept": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/events-intercept/-/events-intercept-2.0.0.tgz", + "integrity": "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==", "dev": true, "license": "MIT" }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/execa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.2.0.tgz", + "integrity": "sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 8" + "node": "^8.12.0 || >=9.7.0" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "node_modules/execa/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==", "dev": true, "license": "ISC" }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "license": "ISC", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/arg": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", - "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "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/async-listen": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-1.2.0.tgz", - "integrity": "sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==", - "dev": true, - "license": "MIT" + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } }, - "node_modules/async-sema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", - "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", + "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==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "file-uri-to-path": "1.0.0" + "bser": "2.1.1" } }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "pend": "~1.2.0" } }, - "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==", + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/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": { - "fill-range": "^7.1.1" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/chokidar": { + "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">=14" }, - "optionalDependencies": { - "fsevents": "~2.1.2" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "license": "ISC" - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "license": "MIT" - }, - "node_modules/code-block-writer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", - "integrity": "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==", - "license": "MIT" - }, - "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" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=6 <7 || >=8" } }, - "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-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" + "dependencies": { + "minipass": "^2.6.0" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-hrtime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-3.0.0.tgz", - "integrity": "sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==", - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "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==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, - "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", + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" }, "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "node_modules/gauge/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==", + "dev": true, + "license": "ISC" }, - "node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "node_modules/generic-pool": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", + "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==", + "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">= 4" } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8.0.0" } }, - "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", + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "license": "BSD-3-Clause", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=0.3.1" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" }, - "node_modules/edge-runtime": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/edge-runtime/-/edge-runtime-2.5.9.tgz", - "integrity": "sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==", - "license": "MPL-2.0", + "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/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@edge-runtime/format": "2.2.1", - "@edge-runtime/ponyfill": "2.4.2", - "@edge-runtime/vm": "3.2.0", - "async-listen": "3.0.1", - "mri": "1.2.0", - "picocolors": "1.0.0", - "pretty-ms": "7.0.1", - "signal-exit": "4.0.2", - "time-span": "4.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, "bin": { - "edge-runtime": "dist/cli/index.js" + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=16" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/edge-runtime/node_modules/async-listen": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.1.tgz", - "integrity": "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "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/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", - "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.47.tgz", - "integrity": "sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==", - "hasInstallScript": true, + "node_modules/http-errors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz", + "integrity": "sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==", + "dev": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "inherits": "2.0.1", + "statuses": ">= 1.2.1 < 2" }, "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.47", - "esbuild-android-arm64": "0.14.47", - "esbuild-darwin-64": "0.14.47", - "esbuild-darwin-arm64": "0.14.47", - "esbuild-freebsd-64": "0.14.47", - "esbuild-freebsd-arm64": "0.14.47", - "esbuild-linux-32": "0.14.47", - "esbuild-linux-64": "0.14.47", - "esbuild-linux-arm": "0.14.47", - "esbuild-linux-arm64": "0.14.47", - "esbuild-linux-mips64le": "0.14.47", - "esbuild-linux-ppc64le": "0.14.47", - "esbuild-linux-riscv64": "0.14.47", - "esbuild-linux-s390x": "0.14.47", - "esbuild-netbsd-64": "0.14.47", - "esbuild-openbsd-64": "0.14.47", - "esbuild-sunos-64": "0.14.47", - "esbuild-windows-32": "0.14.47", - "esbuild-windows-64": "0.14.47", - "esbuild-windows-arm64": "0.14.47" + "node": ">= 0.6" } }, - "node_modules/esbuild-android-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz", - "integrity": "sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==", - "cpu": [ - "x64" - ], + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", + "dev": true, + "license": "ISC" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=12" + "node": ">= 6" } }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz", - "integrity": "sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==", - "cpu": [ - "arm64" - ], + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz", - "integrity": "sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==", - "cpu": [ - "x64" - ], + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz", - "integrity": "sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==", - "cpu": [ - "arm64" - ], + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=12" + "node": ">=0.8.19" } }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz", - "integrity": "sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==", - "cpu": [ - "x64" - ], + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "binary-extensions": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz", - "integrity": "sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==", - "cpu": [ - "arm64" - ], + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild-linux-32": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz", - "integrity": "sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==", - "cpu": [ - "ia32" - ], + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-linux-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz", - "integrity": "sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz", - "integrity": "sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==", - "cpu": [ - "arm" - ], + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz", - "integrity": "sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==", - "cpu": [ - "arm64" - ], + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz", - "integrity": "sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==", - "cpu": [ - "mips64el" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=0.12.0" } }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz", - "integrity": "sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==", - "cpu": [ - "ppc64" - ], + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz", - "integrity": "sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz", - "integrity": "sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz", - "integrity": "sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz", - "integrity": "sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==", - "cpu": [ - "x64" - ], + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "semver": "^7.5.3" + }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz", - "integrity": "sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-windows-32": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz", - "integrity": "sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-windows-64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz", - "integrity": "sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.47", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz", - "integrity": "sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/events-intercept": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/events-intercept/-/events-intercept-2.0.0.tgz", - "integrity": "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/execa": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.2.0.tgz", - "integrity": "sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==", + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" }, "engines": { - "node": "^8.12.0 || >=9.7.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/signal-exit": { + "node_modules/jest-changed-files/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/jest-changed-files/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==", "dev": true, "license": "ISC" }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=8.6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", - "optional": true - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", "dependencies": { - "reusify": "^1.0.4" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "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==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^2.6.0" + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } }, - "node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "node_modules/jest-haste-map/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", @@ -2202,282 +4779,346 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gauge/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==", - "dev": true, - "license": "ISC" - }, - "node_modules/generic-pool": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", - "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">= 4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "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-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/http-errors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz", - "integrity": "sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==", + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "2.0.1", - "statuses": ">= 1.2.1 < 2" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", - "dev": true, - "license": "ISC" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, "engines": { - "node": ">=8.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "@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": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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==", + "dev": true, "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": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "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==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "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==", "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/js-cookie": { @@ -2489,6 +5130,47 @@ "node": ">=14" } }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-to-ts": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.4.tgz", @@ -2507,6 +5189,19 @@ "license": "MIT", "optional": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2517,15 +5212,53 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.3" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2571,16 +5304,14 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "license": "ISC" }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" } }, "node_modules/merge-stream": { @@ -2739,15 +5470,19 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "license": "MIT" }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" }, "node_modules/node-fetch": { "version": "2.6.7", @@ -2781,6 +5516,20 @@ "node-gyp-build-test": "build-test.js" } }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -2890,12 +5639,86 @@ "node": ">=8" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", @@ -2911,6 +5734,16 @@ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "license": "MIT" }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2952,6 +5785,13 @@ "isarray": "0.0.1" } }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -3021,6 +5861,57 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -3043,6 +5934,20 @@ "dev": true, "license": "MIT" }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -3063,6 +5968,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3126,6 +6048,13 @@ "node": ">=0.10.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==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3154,13 +6083,57 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/resolve-from": { @@ -3172,6 +6145,16 @@ "node": ">=8" } }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3313,28 +6296,72 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, "license": "MIT", "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/snakecase-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-8.0.1.tgz", - "integrity": "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==", + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", "dependencies": { - "map-obj": "^4.1.0", - "snake-case": "^3.0.4", - "type-fest": "^4.15.0" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" + } + }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" } }, "node_modules/stat-mode": { @@ -3412,6 +6439,20 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3466,6 +6507,16 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -3476,6 +6527,45 @@ "node": ">=6" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/swr": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", @@ -3515,6 +6605,21 @@ "dev": true, "license": "ISC" }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/time-span": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-4.0.0.tgz", @@ -3530,6 +6635,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "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", @@ -3568,6 +6680,85 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-jest": { + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-morph": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-12.0.0.tgz", @@ -3633,13 +6824,24 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "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==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=16" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3658,6 +6860,20 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid-promise": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/uid-promise/-/uid-promise-1.0.0.tgz", @@ -3703,6 +6919,44 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3745,6 +6999,32 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/vercel": { "version": "33.7.1", "resolved": "https://registry.npmjs.org/vercel/-/vercel-33.7.1.tgz", @@ -3874,6 +7154,16 @@ "node": ">=14.0" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/web-vitals": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz", @@ -3922,6 +7212,13 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4029,6 +7326,27 @@ "dev": true, "license": "ISC" }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/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==", + "dev": true, + "license": "ISC" + }, "node_modules/xdg-app-paths": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-app-paths/-/xdg-app-paths-5.1.0.tgz", @@ -4055,6 +7373,16 @@ "node": ">= 6.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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -4062,6 +7390,35 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -4108,6 +7465,19 @@ "engines": { "node": ">=6" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/backend/package.json b/backend/package.json index f55c392..dcec7b3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,14 +5,22 @@ "node": "20.x" }, "scripts": { - "dev": "vercel dev" + "dev": "vercel dev", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "dependencies": { "@clerk/backend": "^2.12.1", "@vercel/node": "^5.5.4" }, "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.11", "@types/node": "^20.0.0", + "dotenv": "^17.2.3", + "jest": "^29.7.0", + "ts-jest": "^29.1.1", "typescript": "^5.0.0", "vercel": "^33.0.0" } diff --git a/backend/test_rbac.sh b/backend/test_rbac.sh deleted file mode 100644 index 923af9a..0000000 --- a/backend/test_rbac.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Load .env -if [[ -f ".env" ]]; then - set -a - source .env - set +a -else - echo "No .env file found. Please create one." - exit 1 -fi - -# Sanitize Inputs -strip() { printf '%s' "$1" | tr -d '\r\n\t '; } -BASE="$(strip "${BASE:-}")" -CLERK_SECRET_KEY="$(strip "${CLERK_SECRET_KEY:-}")" -STUDENT_ID="$(strip "${STUDENT_ID:-}")" -ADMIN_ID="$(strip "${ADMIN_ID:-}")" - -# Pretty output -BOLD="\033[1m"; RESET="\033[0m" -GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; CYAN="\033[36m" -ok() { echo -e "${GREEN}[OK]${RESET} $1"; } -info() { echo -e "${CYAN}[INFO]${RESET} $1"; } -warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; } -fail() { echo -e "${RED}[ERR]${RESET} $1"; exit 1; } - -# Deps -command -v curl >/dev/null || fail "curl required" -command -v jq >/dev/null || fail "jq required" - -# Clerk API helper -api() { - local method="$1"; shift - local path="$1"; shift - local url="https://api.clerk.com${path}" - curl -sS --fail-with-body -X "$method" "$url" \ - -H "Authorization: Bearer $CLERK_SECRET_KEY" \ - -H "Content-Type: application/json" \ - "$@" -} - -# Session + JWT -ensure_admin() { - info "Ensuring ADMIN has role=admin,status=active" - api GET "/v1/users/$ADMIN_ID" >/dev/null || fail "Admin user not found" - api PATCH "/v1/users/$ADMIN_ID" -d '{"public_metadata":{"role":"admin","status":"active"}}' >/dev/null - ok "Admin metadata set" -} - -reset_student() { - info "Resetting STUDENT to role=student,status=active" - api GET "/v1/users/$STUDENT_ID" >/dev/null || fail "Student user not found" - api PATCH "/v1/users/$STUDENT_ID" -d '{"public_metadata":{"role":"student","status":"active"}}' >/dev/null - ok "Student reset to baseline" -} - -make_token() { - local uid="$1" - local sid - sid=$(api POST "/v1/sessions" -d "{\"user_id\":\"$uid\"}" | jq -r '.id') - api POST "/v1/sessions/$sid/tokens" | jq -r '.jwt' -} - -# API Tests -assert_code() { [[ "$1" == "$2" ]] || fail "Expected HTTP $2, got $1"; } - -test() { - local name="$1"; shift - info "Testing: $name" - "$@" && ok "$name" -} - -test_ping() { - local resp code - resp=$(curl -s -w '|%{http_code}' "$BASE/api/ping") - code="${resp##*|}" - assert_code "$code" "200" -} - -test_me_user() { - local resp code body - resp=$(curl -s -w '|%{http_code}' -H "Authorization: Bearer $USER_JWT" "$BASE/api/me") - code="${resp##*|}" - body="${resp%|*}" - assert_code "$code" "200" - local role=$(echo "$body" | jq -r '.rbac.role') - [[ "$role" == "student" ]] || fail "Expected role=student, got $role" -} - -test_request_role() { - local resp code body - resp=$(curl -s -w '|%{http_code}' \ - -X POST \ - -H "Authorization: Bearer $USER_JWT" \ - "$BASE/api/request-role") - code="${resp##*|}" - body="${resp%|*}" - assert_code "$code" "200" - local status=$(echo "$body" | jq -r '.status') - [[ "$status" == "pending" ]] || fail "Expected status=pending, got $status" -} - -test_pending() { - local resp code body - resp=$(curl -s -w '|%{http_code}' -H "Authorization: Bearer $ADMIN_JWT" "$BASE/api/admin/pending") - code="${resp##*|}" - body="${resp%|*}" - assert_code "$code" "200" - USER_TO_APPROVE=$(echo "$body" | jq -r ".pending[] | select(.userId == \"$STUDENT_ID\") | .userId") - [[ -n "$USER_TO_APPROVE" ]] || fail "Student not found in pending list" -} - -test_approve() { - local resp code body - resp=$(curl -s -w '|%{http_code}' \ - -X POST -H "Authorization: Bearer $ADMIN_JWT" \ - "$BASE/api/admin/approve/$USER_TO_APPROVE") - code="${resp##*|}" - body="${resp%|*}" - assert_code "$code" "200" - - # Verify user is now staff - sleep 1 - resp=$(curl -s -w '|%{http_code}' -H "Authorization: Bearer $USER_JWT" "$BASE/api/me") - body="${resp%|*}" - local role=$(echo "$body" | jq -r '.rbac.role') - [[ "$role" == "staff" ]] || fail "Expected role=staff after approval, got $role" -} - -test_revoke() { - local resp code body - resp=$(curl -s -w '|%{http_code}' \ - -X POST -H "Authorization: Bearer $ADMIN_JWT" \ - "$BASE/api/admin/revoke/$STUDENT_ID") - code="${resp##*|}" - body="${resp%|*}" - assert_code "$code" "200" - - # Verify user is back to student - sleep 1 - resp=$(curl -s -w '|%{http_code}' -H "Authorization: Bearer $USER_JWT" "$BASE/api/me") - body="${resp%|*}" - local role=$(echo "$body" | jq -r '.rbac.role') - [[ "$role" == "student" ]] || fail "Expected role=student after revoke, got $role" -} - -# Cleanup trap -cleanup() { - warn "Cleaning up: Resetting student to baseline state" - reset_student 2>/dev/null || true -} -trap cleanup EXIT - -# Run -echo -e "${BOLD}═══════════════════════════════════════${RESET}" -echo -e "${BOLD} RBAC API TEST SUITE${RESET}" -echo -e "${BOLD} Endpoint: $BASE${RESET}" -echo -e "${BOLD}═══════════════════════════════════════${RESET}" -echo - -ensure_admin -reset_student - -info "Generating JWT tokens..." -USER_JWT=$(make_token "$STUDENT_ID") -ADMIN_JWT=$(make_token "$ADMIN_ID") -ok "Tokens generated" -echo - -test "Ping endpoint" test_ping -test "Get current user info" test_me_user -test "Request staff role" test_request_role -test "Admin: List pending users" test_pending -test "Admin: Approve user" test_approve -test "Admin: Revoke staff access" test_revoke - -echo -echo -e "${BOLD}${GREEN}═══════════════════════════════════════${RESET}" -echo -e "${BOLD}${GREEN} ✓ ALL TESTS PASSED${RESET}" -echo -e "${BOLD}${GREEN}═══════════════════════════════════════${RESET}" -echo From 6886908133cd77c8e498fe1be0e86a70ff36988c Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 19:43:53 -0500 Subject: [PATCH 06/15] ci: add rbac e2e ci pipeline --- .github/workflows/backend-rbac-e2e.yml | 36 ++++++++++++++++++++++++++ .github/workflows/mobile-lint.yml | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/backend-rbac-e2e.yml diff --git a/.github/workflows/backend-rbac-e2e.yml b/.github/workflows/backend-rbac-e2e.yml new file mode 100644 index 0000000..a44e962 --- /dev/null +++ b/.github/workflows/backend-rbac-e2e.yml @@ -0,0 +1,36 @@ +name: Backend RBAC E2E + +on: + push: + branches: [main, dev] + workflow_dispatch: + +jobs: + rbac-e2e: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: backend + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + cache-dependency-path: backend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run RBAC E2E Jest tests + env: + CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} + BASE: ${{ secrets.BACKEND_BASE_URL }} + STUDENT_ID: ${{ secrets.RBAC_STUDENT_ID }} + ADMIN_ID: ${{ secrets.RBAC_ADMIN_ID }} + run: npm test diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml index 8994f35..830c8cd 100644 --- a/.github/workflows/mobile-lint.yml +++ b/.github/workflows/mobile-lint.yml @@ -2,6 +2,7 @@ name: Mobile Lint on: push: + branches: [main, dev] pull_request: jobs: @@ -26,5 +27,5 @@ jobs: - name: Install dependencies run: npm ci - - name: Run Expo lint + - name: Lint with ESLint (Expo) run: npm run lint From 85da3026fa3df6957269c1bb4dd9dde6ae0eb9c3 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 20:01:44 -0500 Subject: [PATCH 07/15] fix: add err handling for clerk API and missing env vars --- backend/__tests__/rbac.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/__tests__/rbac.test.ts b/backend/__tests__/rbac.test.ts index c5be196..463d557 100644 --- a/backend/__tests__/rbac.test.ts +++ b/backend/__tests__/rbac.test.ts @@ -7,6 +7,10 @@ const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY!; const STUDENT_ID = process.env.STUDENT_ID!; const ADMIN_ID = process.env.ADMIN_ID!; +if (!BASE_URL || !CLERK_SECRET_KEY || !STUDENT_ID || !ADMIN_ID) { + throw new Error('Missing required env vars: BASE, CLERK_SECRET_KEY, STUDENT_ID, ADMIN_ID'); +} + let studentToken: string; let adminToken: string; @@ -25,6 +29,13 @@ async function clerkApi(method: string, path: string, body?: any) { body: body ? JSON.stringify(body) : undefined, }); + if (!res.ok) { + const errorText = await res.text(); + throw new Error( + `Clerk API request failed: ${method} ${path} - ${res.status} ${res.statusText}\n${errorText}` + ); + } + return res.json(); } From 760038aa7aa3e72604a4807a5c424ccb5ce5ca67 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 20:03:22 -0500 Subject: [PATCH 08/15] chore: trigger ci workflow --- .github/workflows/backend-rbac-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backend-rbac-e2e.yml b/.github/workflows/backend-rbac-e2e.yml index a44e962..b08e0a3 100644 --- a/.github/workflows/backend-rbac-e2e.yml +++ b/.github/workflows/backend-rbac-e2e.yml @@ -2,7 +2,7 @@ name: Backend RBAC E2E on: push: - branches: [main, dev] + branches: [main, dev, feat/ci-and-tests] workflow_dispatch: jobs: From c721bfd1ea71902ef99fdde597bef2b23037445a Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 20:08:21 -0500 Subject: [PATCH 09/15] fix: correct env names in ci --- .github/workflows/backend-rbac-e2e.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backend-rbac-e2e.yml b/.github/workflows/backend-rbac-e2e.yml index b08e0a3..10dac60 100644 --- a/.github/workflows/backend-rbac-e2e.yml +++ b/.github/workflows/backend-rbac-e2e.yml @@ -29,8 +29,8 @@ jobs: - name: Run RBAC E2E Jest tests env: + BASE: ${{ secrets.BASE }} CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} - BASE: ${{ secrets.BACKEND_BASE_URL }} - STUDENT_ID: ${{ secrets.RBAC_STUDENT_ID }} - ADMIN_ID: ${{ secrets.RBAC_ADMIN_ID }} + STUDENT_ID: ${{ secrets.STUDENT_ID }} + ADMIN_ID: ${{ secrets.ADMIN_ID }} run: npm test From 1f19497417736e721c7271c0cfac3c915311db59 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 22:02:24 -0500 Subject: [PATCH 10/15] chore: test ci --- .github/workflows/mobile-lint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml index 830c8cd..072711d 100644 --- a/.github/workflows/mobile-lint.yml +++ b/.github/workflows/mobile-lint.yml @@ -2,7 +2,6 @@ name: Mobile Lint on: push: - branches: [main, dev] pull_request: jobs: From a02146a787e5d5ecab3aadd8dcad2689e4239358 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 22:04:33 -0500 Subject: [PATCH 11/15] fix: revert mobile linter ci config --- .github/workflows/mobile-lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml index 072711d..830c8cd 100644 --- a/.github/workflows/mobile-lint.yml +++ b/.github/workflows/mobile-lint.yml @@ -2,6 +2,7 @@ name: Mobile Lint on: push: + branches: [main, dev] pull_request: jobs: From d07970b2f0570e3821d4c4a8c826cc407540d2af Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 22:21:31 -0500 Subject: [PATCH 12/15] chore: add README to github workflows folder --- .github/workflows/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/README.md diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..30404ce --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1 @@ +TODO \ No newline at end of file From 40151b1117cb070d700b94abd6c571bce8057aca Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Sun, 16 Nov 2025 22:40:56 -0500 Subject: [PATCH 13/15] docs: update README for class assignment --- .github/workflows/README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 30404ce..9840ab7 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1 +1,29 @@ -TODO \ No newline at end of file +# CI Workflows + +This project uses **GitHub Actions** for continuous integration. + +## Workflows Included + +- **Mobile Lint** + Runs ESLint in the `mobile/` app directory to enforce code quality and catch errors early. + +- **Backend RBAC E2E** + Runs Jest end-to-end tests in `backend/` to verify secure role-based access control. + +## How They Run + +| Workflow | Trigger | +|---------|---------| +| Mobile Lint | Push to `main` / `dev` + all pull requests | +| Backend RBAC E2E | Push to `main` / `dev` or manual trigger | + +## Challenges + +- Ensuring proper working directories for monorepo structure (Fixed using `defaults.run.working-directory`) +- Performance during installs (Fixed by caching Node dependency installs) + +## In the Future + +As we add more testing suites, we will expand our CI pipeline accordingly. + +We plan to include full mobile end-to-end testing using [Detox](https://wix.github.io/Detox/) to ensure the entire app behaves correctly on real devices (soon!). From 4f206ce26e6ae28d9abbd20d9e5895d223d2437d Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Thu, 20 Nov 2025 00:11:49 -0500 Subject: [PATCH 14/15] fix: add logs and remove getToken from dependency array to prevent recursive calls --- mobile/app/welcome.tsx | 74 +++++++++++++----- mobile/src/lib/rbacClient.tsx | 138 ++++++++++++++++++++++++++-------- 2 files changed, 162 insertions(+), 50 deletions(-) diff --git a/mobile/app/welcome.tsx b/mobile/app/welcome.tsx index 32dee08..e5a969b 100644 --- a/mobile/app/welcome.tsx +++ b/mobile/app/welcome.tsx @@ -1,5 +1,5 @@ // app/welcome.tsx -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { firestore } from '../src/lib/firebase/config'; import { doc, setDoc, getDoc } from 'firebase/firestore'; import { @@ -15,6 +15,15 @@ import { useAuth, useUser } from '@clerk/clerk-expo'; import { colors, typography, spacing } from '../src/lib/theme'; import { fetchMe } from '../src/lib/rbacClient'; +const log = (msg: string, data?: any) => { + const timestamp = new Date().toISOString(); + if (data) { + console.log(`[${timestamp}] [welcome.tsx] ${msg}`, data); + } else { + console.log(`[${timestamp}] [welcome.tsx] ${msg}`); + } +}; + export default function WelcomeScreen() { const { isSignedIn, isLoaded: authLoaded, getToken } = useAuth(); const { user, isLoaded: userLoaded } = useUser(); @@ -23,22 +32,44 @@ export default function WelcomeScreen() { ready: boolean; }>({ path: '', ready: false }); + // Use a ref to track if we've already run the check + const hasChecked = useRef(false); + + log('Render', { isSignedIn, authLoaded, userLoaded }); + useEffect(() => { + // Only run once when auth is loaded + if (!authLoaded || !userLoaded || hasChecked.current) { + return; + } + + hasChecked.current = true; + const checkOnboardingStatus = async () => { - if (!authLoaded || !userLoaded) return; + log('checkOnboardingStatus START', { + authLoaded, + userLoaded, + isSignedIn, + }); // Not signed in: stay on welcome screen if (!isSignedIn || !user) { + log('User not signed in, staying on welcome'); setShouldRedirect({ path: '', ready: false }); return; } try { + log('User signed in, checking onboarding status', { userId: user.id }); + // 1) Ensure Firestore user doc exists + log('Fetching Firestore user doc...'); const userRef = doc(firestore, 'Users', user.id); const userSnap = await getDoc(userRef); + log('Firestore fetch complete', { exists: userSnap.exists() }); if (!userSnap.exists()) { + log('Creating new user document...'); await setDoc(userRef, { uid: user.id, email: user.primaryEmailAddress?.emailAddress || '', @@ -51,65 +82,71 @@ export default function WelcomeScreen() { timePref: [], foodPref: [], }); - console.log('✅ New user document created'); + log('✅ New user document created'); setShouldRedirect({ path: '/(onboarding)/onboarding', ready: true }); return; } const userData = userSnap.data(); - console.log(userData.agreedToTerms); + log('User data loaded', { agreedToTerms: userData.agreedToTerms }); + if (userData.agreedToTerms !== true) { - console.log('📋 User needs to complete onboarding'); + log('User needs to complete onboarding'); setShouldRedirect({ path: '/(onboarding)/onboarding', ready: true }); return; } - // 2) User is onboarding, use RBAC backend to decide where to go + // 2) User is onboarded, use RBAC backend to decide where to go + log('User is onboarded, calling fetchMe...'); try { + log('Calling fetchMe with getToken'); const me = await fetchMe(getToken); + log('fetchMe succeeded', me); const rbac = me.rbac; if (!rbac) { - console.log('RBAC missing, falling back to student route'); + log('RBAC missing, falling back to student route'); setShouldRedirect({ path: '/(student)', ready: true }); return; } const { role, status } = rbac; - - console.log('RBAC from /api/me:', role, status); + log('RBAC loaded', { role, status }); if (status === 'pending') { - // Staff request pending, move to pending screen + log('Status is pending, redirecting to pending screen'); setShouldRedirect({ path: '/(onboarding)/pending', ready: true }); return; } if (role === 'admin' || role === 'staff') { - // Staff + admin share the (admin) stack; staff UI will hide admin-only bits + log('Role is admin/staff, redirecting to admin'); setShouldRedirect({ path: '/(admin)', ready: true }); return; } // Default: student experience + log('Role is student, redirecting to student'); setShouldRedirect({ path: '/(student)', ready: true }); } catch (err) { - console.error( - 'Failed to load RBAC info, falling back to student route:', - err, - ); + log('❌ fetchMe failed', err); + console.error('Failed to load RBAC info:', err); + log('Falling back to student route'); setShouldRedirect({ path: '/(student)', ready: true }); } } catch (error) { - console.error('🔥 Failed to check onboarding status:', error); + log('❌ checkOnboardingStatus error', error); + console.error('Failed to check onboarding status:', error); setShouldRedirect({ path: '/(student)', ready: true }); } }; checkOnboardingStatus(); - }, [authLoaded, isSignedIn, userLoaded, user, getToken]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [authLoaded, userLoaded]); if (!authLoaded || !userLoaded) { + log('Still loading auth/user'); return ( @@ -118,10 +155,12 @@ export default function WelcomeScreen() { } if (shouldRedirect.ready && shouldRedirect.path) { + log('Redirecting to', shouldRedirect.path); return ; } if (isSignedIn) { + log('Signed in but no redirect yet (still processing)'); return ( @@ -129,6 +168,7 @@ export default function WelcomeScreen() { ); } + log('Showing welcome screen'); return ( diff --git a/mobile/src/lib/rbacClient.tsx b/mobile/src/lib/rbacClient.tsx index 9c91fda..6e40aad 100644 --- a/mobile/src/lib/rbacClient.tsx +++ b/mobile/src/lib/rbacClient.tsx @@ -22,14 +22,32 @@ export interface RbacUser { status: Status; } -const API_BASE_URL = process.env.EXPO_PUBLIC_BACKEND_URL; +let API_BASE_URL = process.env.EXPO_PUBLIC_BACKEND_URL; +// Fallback for iOS Expo Go env var loading issues if (!API_BASE_URL) { + API_BASE_URL = + 'https://freebites-backend-2a41n9xzn-andrewx-bus-projects.vercel.app'; console.warn( - '[rbacClient] EXPO_PUBLIC_BACKEND_URL is not set. RBAC calls will fail.', + '[rbacClient] ⚠️ EXPO_PUBLIC_BACKEND_URL not found, using hardcoded fallback', ); } +const DEBUG = true; // Toggle for debugging + +const log = (tag: string, msg: string, data?: any) => { + if (DEBUG) { + const timestamp = new Date().toISOString(); + if (data) { + console.log(`[${timestamp}] [${tag}] ${msg}`, data); + } else { + console.log(`[${timestamp}] [${tag}] ${msg}`); + } + } +}; + +console.log('[rbacClient] API_BASE_URL:', API_BASE_URL); + /** * Low-level helper to call Vercel backend with a Clerk Bearer token. */ @@ -38,43 +56,62 @@ async function backendFetch( getToken: () => Promise, init: RequestInit = {}, ): Promise { + log('backendFetch', `Starting fetch: ${path}`); + if (!API_BASE_URL) { + log('backendFetch', '❌ EXPO_PUBLIC_BACKEND_URL is not set'); throw new Error('EXPO_PUBLIC_BACKEND_URL is not set'); } - const token = await getToken(); - if (!token) { - throw new Error('Missing auth token'); - } + try { + log('backendFetch', 'Getting auth token...'); + const token = await getToken(); + log('backendFetch', `Token received: ${token ? 'Yes' : 'No'}`); - const res = await fetch(`${API_BASE_URL}${path}`, { - ...init, - headers: { - 'Content-Type': 'application/json', - ...(init.headers || {}), - Authorization: `Bearer ${token}`, - }, - }); + if (!token) { + log('backendFetch', '❌ Missing auth token'); + throw new Error('Missing auth token'); + } - const text = await res.text(); - let json: any = {}; - if (text) { - try { - json = JSON.parse(text); - } catch { - // ignore parse error; fall back to generic error if needed + const url = `${API_BASE_URL}${path}`; + log('backendFetch', `Fetching from: ${url}`); + + const res = await fetch(url, { + ...init, + headers: { + 'Content-Type': 'application/json', + ...(init.headers || {}), + Authorization: `Bearer ${token}`, + }, + }); + + log('backendFetch', `Response status: ${res.status}`); + + const text = await res.text(); + let json: any = {}; + if (text) { + try { + json = JSON.parse(text); + } catch { + log('backendFetch', '⚠️ Failed to parse response JSON'); + } } - } - if (!res.ok) { - const message = - (json && typeof json.error === 'string' && json.error) || - res.statusText || - 'Request failed'; - throw new Error(message); - } + if (!res.ok) { + const message = + (json && typeof json.error === 'string' && json.error) || + res.statusText || + 'Request failed'; + log('backendFetch', `❌ Request failed: ${message}`); + throw new Error(message); + } - return json as T; + log('backendFetch', `✅ Success: ${path}`, json); + return json as T; + } catch (err) { + log('backendFetch', `❌ Exception in backendFetch`, err); + throw err; + } } /** @@ -82,6 +119,7 @@ async function backendFetch( * Returns { userId, rbac: { role, status } } */ export async function fetchMe(getToken: () => Promise) { + log('fetchMe', 'Starting fetchMe'); return backendFetch('/api/me', getToken); } @@ -91,6 +129,7 @@ export async function fetchMe(getToken: () => Promise) { * Returns { ok: true, status: "pending" } */ export async function requestStaffRole(getToken: () => Promise) { + log('requestStaffRole', 'Starting requestStaffRole'); return backendFetch<{ ok: boolean; status: Status }>( '/api/request-role', getToken, @@ -105,6 +144,7 @@ export async function requestStaffRole(getToken: () => Promise) { export async function fetchPendingStaff( getToken: () => Promise, ) { + log('fetchPendingStaff', 'Starting fetchPendingStaff'); return backendFetch<{ pending: RbacUser[] }>('/api/admin/pending', getToken); } @@ -113,6 +153,7 @@ export async function fetchPendingStaff( * Returns { staff: RbacUser[] } (active staff) */ export async function fetchActiveStaff(getToken: () => Promise) { + log('fetchActiveStaff', 'Starting fetchActiveStaff'); return backendFetch<{ staff: RbacUser[] }>('/api/admin/staff', getToken); } @@ -125,6 +166,7 @@ export async function approveStaff( getToken: () => Promise, ) { if (!uid) throw new Error('Missing user id'); + log('approveStaff', `Approving staff: ${uid}`); return backendFetch<{ ok: boolean; userId: string }>( `/api/admin/approve/${uid}`, getToken, @@ -142,6 +184,7 @@ export async function revokeStaff( getToken: () => Promise, ) { if (!uid) throw new Error('Missing user id'); + log('revokeStaff', `Revoking staff: ${uid}`); return backendFetch<{ ok: boolean; userId: string }>( `/api/admin/revoke/${uid}`, getToken, @@ -159,6 +202,7 @@ let inFlightPromise: Promise | null = null; const listeners = new Set<() => void>(); function notifyListeners() { + log('rbacClient', `Notifying ${listeners.size} listeners`); for (const listener of listeners) { try { listener(); @@ -172,8 +216,14 @@ async function fetchRbacInternal( getToken: () => Promise, force = false, ): Promise { + log('fetchRbacInternal', `Called with force=${force}`, { + inFlightPromise: !!inFlightPromise, + rbacLoading, + }); + // Reuse in-flight request if not forcing if (inFlightPromise && !force) { + log('fetchRbacInternal', 'Reusing in-flight promise'); return inFlightPromise; } @@ -183,18 +233,25 @@ async function fetchRbacInternal( inFlightPromise = (async () => { try { + log('fetchRbacInternal', 'Calling backendFetch...'); const res = await backendFetch('/api/me', getToken); rbacUserId = res.userId ?? null; rbacData = res.rbac ?? null; + log('fetchRbacInternal', '✅ RBAC data loaded', { + userId: rbacUserId, + role: rbacData?.role, + }); } catch (err: any) { console.error('[rbacClient] Failed to load RBAC info:', err); rbacError = err?.message || 'Failed to load RBAC info'; rbacUserId = null; rbacData = null; + log('fetchRbacInternal', `❌ Error: ${rbacError}`); } finally { rbacLoading = false; inFlightPromise = null; notifyListeners(); + log('fetchRbacInternal', 'fetchRbacInternal complete'); } })(); @@ -210,20 +267,27 @@ export function useRbac() { const { isSignedIn, getToken } = useAuth(); const [, setVersion] = useState(0); + log('useRbac', 'Hook rendered', { isSignedIn }); + useEffect(() => { + log('useRbac', 'useEffect triggered', { isSignedIn, rbacLoading }); + const listener = () => { + log('useRbac', 'State changed, re-rendering'); setVersion((v) => v + 1); }; listeners.add(listener); - // Always (re)load once per signed-in session, even if rbacData is already set + // Always (re)load once per signed-in session if (isSignedIn && !rbacLoading) { + log('useRbac', 'Initiating fetchRbacInternal...'); fetchRbacInternal(getToken, true); } - // Clear stale cache on sign-out so a future sign-in doesn't reuse it + // Clear stale cache on sign-out if (!isSignedIn) { + log('useRbac', 'Clearing RBAC cache (signed out)'); rbacData = null; rbacUserId = null; rbacError = null; @@ -232,11 +296,14 @@ export function useRbac() { } return () => { + log('useRbac', 'Cleanup: removing listener'); listeners.delete(listener); }; - }, [isSignedIn, getToken]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSignedIn]); const refresh = useCallback(() => { + log('useRbac', 'refresh() called', { isSignedIn }); if (!isSignedIn) return; fetchRbacInternal(getToken, true); }, [isSignedIn, getToken]); @@ -252,6 +319,11 @@ export function useRbac() { } const loading = rbacLoading || (!rbacData && !rbacError); + log('useRbac', 'Returning state', { + loading, + error: rbacError, + role: rbacData?.role, + }); return { loading, From a82d1b6d775b1f3f8d488bcaca2a4577dfaa88d8 Mon Sep 17 00:00:00 2001 From: andrewx-bu Date: Thu, 20 Nov 2025 00:20:03 -0500 Subject: [PATCH 15/15] chore: add simple index for redirect & debugging --- mobile/app/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 mobile/app/index.tsx diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx new file mode 100644 index 0000000..1237a8a --- /dev/null +++ b/mobile/app/index.tsx @@ -0,0 +1,8 @@ +// app/index.tsx - Simple entry point that redirects to welcome +import { Redirect } from 'expo-router'; + +console.log('index.tsx loaded - redirecting to welcome'); + +export default function IndexScreen() { + return ; +}