From b1a556dc6ddd5c6818bd39a9e166491738ed2833 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Sat, 13 Dec 2025 23:26:13 +0100 Subject: [PATCH] project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application. --- .cursor/commands/about.md | 0 .cursor/context.md | 174 + .gitignore | 150 +- README.md | 443 +- astro.config.mjs | 29 + docs/ARCHITECTURE_SCALING.md | 395 + middlelayer/IMPROVEMENTS.md | 133 + middlelayer/__cms/Contentful_Badges.ts | 14 + middlelayer/__cms/Contentful_Campaign.ts | 24 + middlelayer/__cms/Contentful_Campaigns.ts | 13 + .../__cms/Contentful_CloudinaryImage.ts | 15 + middlelayer/__cms/Contentful_Content.ts | 24 + .../__cms/Contentful_ContentType.enum.ts | 31 + middlelayer/__cms/Contentful_Footer.ts | 10 + .../__cms/Contentful_FullwidthBanner.ts | 24 + middlelayer/__cms/Contentful_Grid.ts | 29 + middlelayer/__cms/Contentful_Headline.ts | 21 + middlelayer/__cms/Contentful_Html.ts | 13 + middlelayer/__cms/Contentful_Iframe.ts | 16 + middlelayer/__cms/Contentful_Image.ts | 17 + middlelayer/__cms/Contentful_ImageGallery.ts | 15 + middlelayer/__cms/Contentful_Img.ts | 26 + .../__cms/Contentful_InternalReference.ts | 14 + middlelayer/__cms/Contentful_Layout.ts | 100 + middlelayer/__cms/Contentful_Link.ts | 23 + middlelayer/__cms/Contentful_Link_List.ts | 12 + middlelayer/__cms/Contentful_List.ts | 11 + middlelayer/__cms/Contentful_Markdown.ts | 15 + middlelayer/__cms/Contentful_Names.enum.ts | 17 + middlelayer/__cms/Contentful_Navigation.ts | 14 + middlelayer/__cms/Contentful_Page.ts | 19 + middlelayer/__cms/Contentful_PageConfig.ts | 18 + middlelayer/__cms/Contentful_Page_Seo.ts | 7 + middlelayer/__cms/Contentful_Picture.ts | 57 + middlelayer/__cms/Contentful_Post.ts | 25 + .../__cms/Contentful_Post_Component.ts | 14 + middlelayer/__cms/Contentful_Post_Overview.ts | 24 + middlelayer/__cms/Contentful_Quote.ts | 14 + middlelayer/__cms/Contentful_Richtext.ts | 11 + middlelayer/__cms/Contentful_SEO.ts | 8 + middlelayer/__cms/Contentful_SkeletonTypes.ts | 6 + middlelayer/__cms/Contentful_Tag.ts | 11 + middlelayer/__cms/Contentful_YoutubeVideo.ts | 16 + middlelayer/__cms/SeoProps.ts | 72 + middlelayer/__cms/TextAlignment.ts | 6 + .../adapters/Mock/_cms/mockNavigation.ts | 37 + middlelayer/adapters/Mock/_cms/mockPage.ts | 871 ++ .../adapters/Mock/_cms/mockPageConfig.ts | 50 + .../adapters/Mock/_cms/mockProducts.ts | 113 + .../adapters/Mock/_i18n/mockTranslations.ts | 191 + middlelayer/adapters/Mock/mockdata.ts | 93 + middlelayer/adapters/config.ts | 23 + middlelayer/adapters/interface.ts | 27 + middlelayer/auth/README.md | 150 + middlelayer/auth/authorization.ts | 59 + middlelayer/auth/jwt.ts | 63 + middlelayer/auth/password.ts | 31 + middlelayer/auth/userService.ts | 140 + middlelayer/config/cache.ts | 19 + middlelayer/dataService.ts | 129 + middlelayer/index.ts | 119 + middlelayer/mappers/pageMapper.ts | 235 + middlelayer/monitoring/README.md | 112 + middlelayer/monitoring/logger.ts | 86 + middlelayer/monitoring/metrics.ts | 90 + middlelayer/monitoring/tracing.ts | 78 + middlelayer/plugins/monitoring.ts | 101 + middlelayer/plugins/queryComplexity.ts | 67 + middlelayer/plugins/responseCache.ts | 32 + middlelayer/resolvers.ts | 207 + middlelayer/schema.ts | 217 + middlelayer/types/README.md | 98 + middlelayer/types/c_headline.ts | 10 + middlelayer/types/c_html.ts | 8 + middlelayer/types/c_iframe.ts | 10 + middlelayer/types/c_image.ts | 11 + middlelayer/types/c_imageGallery.ts | 13 + middlelayer/types/c_markdown.ts | 9 + middlelayer/types/c_quote.ts | 9 + middlelayer/types/c_youtubeVideo.ts | 11 + middlelayer/types/cms/CloudinaryImage.ts | 16 + middlelayer/types/cms/Content.ts | 42 + middlelayer/types/cms/ContentType.enum.ts | 32 + middlelayer/types/cms/FullwidthBanner.ts | 24 + middlelayer/types/cms/Headline.ts | 21 + middlelayer/types/cms/Html.ts | 14 + middlelayer/types/cms/Iframe.ts | 17 + middlelayer/types/cms/Image.ts | 18 + middlelayer/types/cms/ImageGallery.ts | 16 + middlelayer/types/cms/Img.ts | 26 + middlelayer/types/cms/Layout.ts | 13 + middlelayer/types/cms/Link.ts | 9 + middlelayer/types/cms/Markdown.ts | 16 + middlelayer/types/cms/Navigation.ts | 15 + middlelayer/types/cms/Page.ts | 20 + middlelayer/types/cms/PageConfig.ts | 19 + middlelayer/types/cms/Quote.ts | 15 + middlelayer/types/cms/SEO.ts | 12 + middlelayer/types/cms/TextAlignment.ts | 7 + middlelayer/types/cms/YoutubeVideo.ts | 17 + middlelayer/types/cms/index.ts | 22 + middlelayer/types/contentLayout.ts | 6 + middlelayer/types/index.ts | 4 + middlelayer/types/navigation.ts | 15 + middlelayer/types/page.ts | 48 + middlelayer/types/pageSeo.ts | 7 + middlelayer/types/product.ts | 17 + middlelayer/types/user.ts | 45 + middlelayer/utils/REDIS_SETUP.md | 82 + middlelayer/utils/cache.ts | 82 + middlelayer/utils/cacheKeys.ts | 33 + middlelayer/utils/dataServiceHelpers.ts | 80 + middlelayer/utils/dataloaders.ts | 89 + middlelayer/utils/errors.ts | 28 + middlelayer/utils/redisCache.ts | 175 + package-lock.json | 9037 +++++++++++++++++ package.json | 47 + public/favicon.svg | 9 + src/assets/astro.svg | 1 + src/assets/background.svg | 1 + src/components/ContentCol.astro | 37 + src/components/ContentRow.astro | 45 + src/components/Footer.astro | 26 + src/components/LanguageSwitcher.tsx | 46 + src/components/LoginModal.tsx | 220 + src/components/PageContent.astro | 68 + src/components/Product.astro | 96 + src/components/RegisterModal.tsx | 278 + src/components/Welcome.astro | 210 + src/components/cms/C_headline.astro | 23 + src/components/cms/C_html.astro | 13 + src/components/cms/C_iframe.astro | 32 + src/components/cms/C_image.astro | 33 + src/components/cms/C_imageGallery.astro | 39 + src/components/cms/C_markdown.astro | 20 + src/components/cms/C_quote.astro | 22 + src/components/cms/C_youtubeVideo.astro | 29 + src/env.d.ts | 9 + src/layouts/Layout.astro | 218 + src/lib/alpine/loginModal.ts | 144 + src/lib/alpine/registerModal.ts | 164 + src/lib/auth/authService.ts | 91 + src/lib/graphql/authQueries.ts | 97 + src/lib/graphql/client.ts | 115 + src/lib/graphql/cmsQueries.ts | 311 + src/lib/graphql/i18nQueries.ts | 47 + src/lib/graphql/queries.ts | 62 + src/lib/i18n/alpine.ts | 23 + src/lib/i18n/defaults.ts | 66 + src/lib/i18n/i18n.ts | 189 + src/lib/i18n/useI18n.tsx | 25 + src/lib/types/navigation.ts | 2 + src/lib/types/page.ts | 9 + src/lib/types/pageSeo.ts | 2 + src/lib/types/product.ts | 2 + src/lib/utils/layoutClasses.ts | 74 + src/lib/utils/markdown.ts | 12 + src/lib/utils/pageLoader.ts | 43 + src/middleware.ts | 44 + src/pages/[locale]/404.astro | 42 + src/pages/[locale]/500.astro | 41 + src/pages/[locale]/[...slug].astro | 139 + src/pages/[locale]/index.astro | 37 + src/pages/[locale]/products/index.astro | 64 + src/styles/global.css | 1 + src/types/global.d.ts | 17 + tsconfig.json | 15 + 167 files changed, 19057 insertions(+), 131 deletions(-) create mode 100644 .cursor/commands/about.md create mode 100644 .cursor/context.md create mode 100644 astro.config.mjs create mode 100644 docs/ARCHITECTURE_SCALING.md create mode 100644 middlelayer/IMPROVEMENTS.md create mode 100644 middlelayer/__cms/Contentful_Badges.ts create mode 100644 middlelayer/__cms/Contentful_Campaign.ts create mode 100644 middlelayer/__cms/Contentful_Campaigns.ts create mode 100644 middlelayer/__cms/Contentful_CloudinaryImage.ts create mode 100644 middlelayer/__cms/Contentful_Content.ts create mode 100644 middlelayer/__cms/Contentful_ContentType.enum.ts create mode 100644 middlelayer/__cms/Contentful_Footer.ts create mode 100644 middlelayer/__cms/Contentful_FullwidthBanner.ts create mode 100644 middlelayer/__cms/Contentful_Grid.ts create mode 100644 middlelayer/__cms/Contentful_Headline.ts create mode 100644 middlelayer/__cms/Contentful_Html.ts create mode 100644 middlelayer/__cms/Contentful_Iframe.ts create mode 100644 middlelayer/__cms/Contentful_Image.ts create mode 100644 middlelayer/__cms/Contentful_ImageGallery.ts create mode 100644 middlelayer/__cms/Contentful_Img.ts create mode 100644 middlelayer/__cms/Contentful_InternalReference.ts create mode 100644 middlelayer/__cms/Contentful_Layout.ts create mode 100644 middlelayer/__cms/Contentful_Link.ts create mode 100644 middlelayer/__cms/Contentful_Link_List.ts create mode 100644 middlelayer/__cms/Contentful_List.ts create mode 100644 middlelayer/__cms/Contentful_Markdown.ts create mode 100644 middlelayer/__cms/Contentful_Names.enum.ts create mode 100644 middlelayer/__cms/Contentful_Navigation.ts create mode 100644 middlelayer/__cms/Contentful_Page.ts create mode 100644 middlelayer/__cms/Contentful_PageConfig.ts create mode 100644 middlelayer/__cms/Contentful_Page_Seo.ts create mode 100644 middlelayer/__cms/Contentful_Picture.ts create mode 100644 middlelayer/__cms/Contentful_Post.ts create mode 100644 middlelayer/__cms/Contentful_Post_Component.ts create mode 100644 middlelayer/__cms/Contentful_Post_Overview.ts create mode 100644 middlelayer/__cms/Contentful_Quote.ts create mode 100644 middlelayer/__cms/Contentful_Richtext.ts create mode 100644 middlelayer/__cms/Contentful_SEO.ts create mode 100644 middlelayer/__cms/Contentful_SkeletonTypes.ts create mode 100644 middlelayer/__cms/Contentful_Tag.ts create mode 100644 middlelayer/__cms/Contentful_YoutubeVideo.ts create mode 100644 middlelayer/__cms/SeoProps.ts create mode 100644 middlelayer/__cms/TextAlignment.ts create mode 100644 middlelayer/adapters/Mock/_cms/mockNavigation.ts create mode 100644 middlelayer/adapters/Mock/_cms/mockPage.ts create mode 100644 middlelayer/adapters/Mock/_cms/mockPageConfig.ts create mode 100644 middlelayer/adapters/Mock/_cms/mockProducts.ts create mode 100644 middlelayer/adapters/Mock/_i18n/mockTranslations.ts create mode 100644 middlelayer/adapters/Mock/mockdata.ts create mode 100644 middlelayer/adapters/config.ts create mode 100644 middlelayer/adapters/interface.ts create mode 100644 middlelayer/auth/README.md create mode 100644 middlelayer/auth/authorization.ts create mode 100644 middlelayer/auth/jwt.ts create mode 100644 middlelayer/auth/password.ts create mode 100644 middlelayer/auth/userService.ts create mode 100644 middlelayer/config/cache.ts create mode 100644 middlelayer/dataService.ts create mode 100644 middlelayer/index.ts create mode 100644 middlelayer/mappers/pageMapper.ts create mode 100644 middlelayer/monitoring/README.md create mode 100644 middlelayer/monitoring/logger.ts create mode 100644 middlelayer/monitoring/metrics.ts create mode 100644 middlelayer/monitoring/tracing.ts create mode 100644 middlelayer/plugins/monitoring.ts create mode 100644 middlelayer/plugins/queryComplexity.ts create mode 100644 middlelayer/plugins/responseCache.ts create mode 100644 middlelayer/resolvers.ts create mode 100644 middlelayer/schema.ts create mode 100644 middlelayer/types/README.md create mode 100644 middlelayer/types/c_headline.ts create mode 100644 middlelayer/types/c_html.ts create mode 100644 middlelayer/types/c_iframe.ts create mode 100644 middlelayer/types/c_image.ts create mode 100644 middlelayer/types/c_imageGallery.ts create mode 100644 middlelayer/types/c_markdown.ts create mode 100644 middlelayer/types/c_quote.ts create mode 100644 middlelayer/types/c_youtubeVideo.ts create mode 100644 middlelayer/types/cms/CloudinaryImage.ts create mode 100644 middlelayer/types/cms/Content.ts create mode 100644 middlelayer/types/cms/ContentType.enum.ts create mode 100644 middlelayer/types/cms/FullwidthBanner.ts create mode 100644 middlelayer/types/cms/Headline.ts create mode 100644 middlelayer/types/cms/Html.ts create mode 100644 middlelayer/types/cms/Iframe.ts create mode 100644 middlelayer/types/cms/Image.ts create mode 100644 middlelayer/types/cms/ImageGallery.ts create mode 100644 middlelayer/types/cms/Img.ts create mode 100644 middlelayer/types/cms/Layout.ts create mode 100644 middlelayer/types/cms/Link.ts create mode 100644 middlelayer/types/cms/Markdown.ts create mode 100644 middlelayer/types/cms/Navigation.ts create mode 100644 middlelayer/types/cms/Page.ts create mode 100644 middlelayer/types/cms/PageConfig.ts create mode 100644 middlelayer/types/cms/Quote.ts create mode 100644 middlelayer/types/cms/SEO.ts create mode 100644 middlelayer/types/cms/TextAlignment.ts create mode 100644 middlelayer/types/cms/YoutubeVideo.ts create mode 100644 middlelayer/types/cms/index.ts create mode 100644 middlelayer/types/contentLayout.ts create mode 100644 middlelayer/types/index.ts create mode 100644 middlelayer/types/navigation.ts create mode 100644 middlelayer/types/page.ts create mode 100644 middlelayer/types/pageSeo.ts create mode 100644 middlelayer/types/product.ts create mode 100644 middlelayer/types/user.ts create mode 100644 middlelayer/utils/REDIS_SETUP.md create mode 100644 middlelayer/utils/cache.ts create mode 100644 middlelayer/utils/cacheKeys.ts create mode 100644 middlelayer/utils/dataServiceHelpers.ts create mode 100644 middlelayer/utils/dataloaders.ts create mode 100644 middlelayer/utils/errors.ts create mode 100644 middlelayer/utils/redisCache.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.svg create mode 100644 src/assets/astro.svg create mode 100644 src/assets/background.svg create mode 100644 src/components/ContentCol.astro create mode 100644 src/components/ContentRow.astro create mode 100644 src/components/Footer.astro create mode 100644 src/components/LanguageSwitcher.tsx create mode 100644 src/components/LoginModal.tsx create mode 100644 src/components/PageContent.astro create mode 100644 src/components/Product.astro create mode 100644 src/components/RegisterModal.tsx create mode 100644 src/components/Welcome.astro create mode 100644 src/components/cms/C_headline.astro create mode 100644 src/components/cms/C_html.astro create mode 100644 src/components/cms/C_iframe.astro create mode 100644 src/components/cms/C_image.astro create mode 100644 src/components/cms/C_imageGallery.astro create mode 100644 src/components/cms/C_markdown.astro create mode 100644 src/components/cms/C_quote.astro create mode 100644 src/components/cms/C_youtubeVideo.astro create mode 100644 src/env.d.ts create mode 100644 src/layouts/Layout.astro create mode 100644 src/lib/alpine/loginModal.ts create mode 100644 src/lib/alpine/registerModal.ts create mode 100644 src/lib/auth/authService.ts create mode 100644 src/lib/graphql/authQueries.ts create mode 100644 src/lib/graphql/client.ts create mode 100644 src/lib/graphql/cmsQueries.ts create mode 100644 src/lib/graphql/i18nQueries.ts create mode 100644 src/lib/graphql/queries.ts create mode 100644 src/lib/i18n/alpine.ts create mode 100644 src/lib/i18n/defaults.ts create mode 100644 src/lib/i18n/i18n.ts create mode 100644 src/lib/i18n/useI18n.tsx create mode 100644 src/lib/types/navigation.ts create mode 100644 src/lib/types/page.ts create mode 100644 src/lib/types/pageSeo.ts create mode 100644 src/lib/types/product.ts create mode 100644 src/lib/utils/layoutClasses.ts create mode 100644 src/lib/utils/markdown.ts create mode 100644 src/lib/utils/pageLoader.ts create mode 100644 src/middleware.ts create mode 100644 src/pages/[locale]/404.astro create mode 100644 src/pages/[locale]/500.astro create mode 100644 src/pages/[locale]/[...slug].astro create mode 100644 src/pages/[locale]/index.astro create mode 100644 src/pages/[locale]/products/index.astro create mode 100644 src/styles/global.css create mode 100644 src/types/global.d.ts create mode 100644 tsconfig.json diff --git a/.cursor/commands/about.md b/.cursor/commands/about.md new file mode 100644 index 0000000..e69de29 diff --git a/.cursor/context.md b/.cursor/context.md new file mode 100644 index 0000000..579bd9f --- /dev/null +++ b/.cursor/context.md @@ -0,0 +1,174 @@ +# Projekt-Kontext: GraphQL Middlelayer + Astro Frontend + +## Projekt-Übersicht + +Ein GraphQL-basierter Middlelayer mit Astro-Frontend für einen Onlineshop. Der Middlelayer fungiert als Abstraktionsschicht zwischen Frontend und verschiedenen Datenquellen (aktuell Mock-Daten, später Headless CMS, etc.). + +## Architektur + +### Middlelayer (`middlelayer/`) +- **GraphQL Server** (Apollo Server v5) auf Port 4000 +- **Adapter Pattern** für Datenquellen-Abstraktion +- **DataService** als Singleton-Aggregator +- **Monitoring & Observability**: + - Structured Logging (Winston) + - Prometheus Metrics (Port 9090) + - Distributed Tracing +- **Performance-Optimierungen**: + - Dataloader für Batch-Loading (verhindert N+1 Queries) + - Response Caching + - Query Complexity Limits (mit Workaround für Schema-Realm-Konflikt) + +### Frontend (`src/`) +- **Astro Framework** mit SSR +- **Tailwind CSS** für Styling +- **Alpine.js** für Client-Side Interaktivität +- **GraphQL Client** für Datenabfragen + +## Datenstrukturen + +### Produkte +- `Product` mit `originalPrice` (Streichpreis) und `promotion` (Objekt mit `category` und `text`) +- Promotion-Typen: `"sale"` oder `"topseller"` +- Promotion-Text: z.B. `"-30%"` oder `"top"` + +### CMS-Daten +- `Page` - Seiten mit SEO, Headlines, Bannern (mit Locale-Support) +- `PageSeo` - SEO-Metadaten (mit Locale-Support) +- `Navigation` - Navigationsstruktur mit Links (mit Locale-Support) + +### Content-System +- **CMS-Typen** (`middlelayer/types/cms/`) - Struktur wie Daten vom CMS kommen + - `*Skeleton` - Wrapper mit `contentTypeId` und `fields` + - Verwendet `ComponentLayout` (Alias für `contentLayout`) + - Nur für Mapper und Mock-Daten +- **Domain-Typen** (`middlelayer/types/c_*.ts`) - Struktur wie Daten in der App verwendet werden + - `c_*` - Content-Item-Typen mit `type`-Feld für Discriminated Union + - Verwendet `contentLayout` direkt + - Für GraphQL Schema, Astro Components, etc. +- **Content-Komponenten**: HTML, Markdown, Iframe, ImageGallery, Image, Quote, YoutubeVideo, Headline +- **Mapper** (`middlelayer/mappers/pageMapper.ts`) - Konvertiert CMS-Typen zu Domain-Typen + +### Internationalization (i18n) +- **URL-basierte Locales**: `/de` und `/en` URLs +- **Übersetzungen**: Defaults + Middlelayer-Überschreibungen +- **React**: `useI18n()` Hook für Komponenten +- **Alpine.js**: `window.i18n.t()` für Navigation +- **CMS**: Locale-Parameter in allen CMS-Queries +- **Contentful-Ansatz**: Vorbereitet für Contentful Locale-System (jedes Feld lokalisiert) + +## Wichtige Dateien + +### Middlelayer +- `middlelayer/index.ts` - Server-Entry-Point +- `middlelayer/schema.ts` - GraphQL Schema +- `middlelayer/resolvers.ts` - GraphQL Resolvers +- `middlelayer/dataService.ts` - DataService (Singleton) +- `middlelayer/adapters/interface.ts` - DataAdapter Interface +- `middlelayer/adapters/mockdata.ts` - MockdataAdapter +- `middlelayer/adapters/Mock/_cms/` - Mock-Daten für CMS +- `middlelayer/mappers/pageMapper.ts` - Konvertiert CMS-Typen zu Domain-Typen +- `middlelayer/types/cms/` - CMS-spezifische Typen (für Mapper/Mock) +- `middlelayer/types/c_*.ts` - Domain-Typen (für App) +- `middlelayer/types/contentLayout.ts` - Einheitlicher Layout-Typ +- `middlelayer/utils/dataloaders.ts` - Dataloader für Batch-Loading + GraphQLContext +- `middlelayer/utils/cache.ts` - In-Memory Cache mit Metrics +- `middlelayer/monitoring/` - Logging, Metrics, Tracing +- `middlelayer/plugins/` - Apollo Server Plugins + +### Frontend +- `src/components/Product.astro` - Produkt-Komponente mit Promotion & Streichpreis +- `src/components/LoginModal.tsx` - React Login-Modal mit i18n +- `src/components/RegisterModal.tsx` - React Register-Modal mit i18n +- `src/lib/graphql/client.ts` - GraphQL Client +- `src/lib/graphql/queries.ts` - Produkt-Queries +- `src/lib/graphql/cmsQueries.ts` - CMS-Queries (mit Locale-Support) +- `src/lib/i18n/` - i18n-System (defaults, i18n.ts, useI18n.tsx, alpine.ts) +- `src/layouts/Layout.astro` - Layout mit Navigation & SEO +- `src/middleware.ts` - URL-basierte Locale-Routing +- `src/pages/[locale]/` - Locale-basierte Routen + +## Konfiguration + +### Environment Variables +```bash +PORT=4000 # GraphQL Server Port +METRICS_PORT=9090 # Metrics Server Port +LOG_LEVEL=info # Log-Level (debug, info, warn, error) +MAX_QUERY_COMPLEXITY=1000 # Query Complexity Limit +NODE_ENV=development # Environment +``` + +### Cache-TTLs (konfigurierbar via Environment oder `middlelayer/config/cache.ts`) +- Pages: 60 Sekunden +- SEO/Navigation: 5 Minuten +- Products: 30 Sekunden + +## Bekannte Probleme & Workarounds + +1. **Query Complexity Schema-Realm-Konflikt** + - Problem: `graphql-query-complexity` hat Schema-Realm-Konflikt mit Apollo Server + - Workaround: Plugin überspringt Check bei Realm-Konflikten (siehe `middlelayer/plugins/queryComplexity.ts`) + - Status: Funktioniert, aber Complexity-Check wird manchmal übersprungen + +2. **GraphQL Version** + - Alle Pakete verwenden `graphql@16.12.0` + - `overrides` in `package.json` stellt sicher, dass nur eine Version verwendet wird + +## NPM Scripts + +```bash +npm run mock:server # Startet nur GraphQL Server +npm start # Startet GraphQL Server + Astro (concurrently) +npm run dev # Startet nur Astro +``` + +## Endpoints + +- **GraphQL**: `http://localhost:4000` +- **GraphQL Playground**: `http://localhost:4000` +- **Metrics**: `http://localhost:9090/metrics` +- **Health Check**: `http://localhost:9090/health` +- **Astro**: `http://localhost:4321` + +## Implementierte Features + +✅ GraphQL Server mit Apollo Server v5 +✅ Adapter Pattern für Datenquellen +✅ Mock-Daten für Produkte und CMS +✅ Product-Komponente mit Promotion & Streichpreis +✅ Structured Logging (Winston) +✅ Prometheus Metrics +✅ Distributed Tracing +✅ Dataloader für Batch-Loading +✅ Response Caching +✅ Query Complexity Limits (mit Workaround) +✅ Cache mit Metrics-Tracking +✅ DataService mit Metrics-Tracking + +## Nächste Schritte / Offene Punkte + +- [ ] Query Complexity Schema-Realm-Problem dauerhaft lösen +- [ ] Redis Cache Integration (statt In-Memory) +- [ ] Database Adapter (für echte Datenbank) +- [ ] Headless CMS Adapter (Contentful, Strapi, etc.) +- [ ] Rate Limiting +- [ ] Authentication/Authorization +- [ ] GraphQL Subscriptions (falls benötigt) + +## Wichtige Design-Entscheidungen + +1. **Adapter Pattern**: Ermöglicht einfaches Wechseln zwischen Datenquellen +2. **Singleton DataService**: Zentrale Stelle für alle Datenoperationen +3. **Monitoring First**: Von Anfang an Monitoring integriert +4. **Type Safety**: TypeScript durchgängig verwendet +5. **Structured Logging**: JSON-Logs für bessere Analyse +6. **Metrics**: Prometheus-kompatible Metriken für Monitoring + +## Code-Stil + +- TypeScript mit ES Modules +- Doppelte Anführungszeichen für Strings (Prettier) +- Deutsche Kommentare und Fehlermeldungen +- Englische Code-Namen und API-Namen + diff --git a/.gitignore b/.gitignore index 2309cc8..60371dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,138 +1,28 @@ -# ---> Node -# Logs -logs -*.log +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs npm-debug.log* yarn-debug.log* yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* +pnpm-debug.log* -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +# environment variables .env -.env.development.local -.env.test.local -.env.production.local -.env.local +.env.production -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache +# macOS-specific files +.DS_Store -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +# jetbrains setting folder +.idea/ +# vscode settings folder +.vscode/ +.history/ \ No newline at end of file diff --git a/README.md b/README.md index 3fec2f6..c455e85 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,443 @@ -# sell +# GraphQL Middlelayer + Astro Frontend +Ein moderner, skalierbarer Onlineshop mit GraphQL-basiertem Middlelayer und Astro-Frontend. Der Middlelayer fungiert als flexible Abstraktionsschicht zwischen Frontend und verschiedenen Datenquellen. + +## 🚀 Features + +- **GraphQL API** mit Apollo Server v5 +- **Astro Frontend** mit SSR und Client-Side Interaktivität +- **Adapter Pattern** für flexible Datenquellen (Mock, Headless CMS, Database) +- **Performance-Optimierungen**: + - Dataloader für Batch-Loading (verhindert N+1 Queries) + - Redis/In-Memory Caching + - Response Caching + - Query Complexity Limits +- **Monitoring & Observability**: + - Structured Logging (Winston) + - Prometheus Metrics + - Distributed Tracing +- **Type-Safe** mit TypeScript durchgängig +- **Modern UI** mit Tailwind CSS und Alpine.js +- **Internationalization (i18n)**: + - URL-basierte Locales (`/de`, `/en`) + - Übersetzungen aus Middlelayer (mit Default-Fallback) + - CMS-Inhalte mehrsprachig (vorbereitet für Contentful) + +## 📋 Voraussetzungen + +- Node.js 18+ +- npm oder yarn +- (Optional) Redis für verteiltes Caching + +## 🛠️ Installation + +```bash +# Dependencies installieren +npm install + +# (Optional) Redis installieren (macOS) +brew install redis +brew services start redis +``` + +## 🏃 Quick Start + +```bash +# GraphQL Server + Astro Frontend starten +npm start + +# Oder einzeln: +npm run mock:server # Nur GraphQL Server (Port 4000) +npm run dev # Nur Astro Frontend (Port 4321) +``` + +## 📁 Projektstruktur + +``` +/ +├── middlelayer/ # GraphQL Middlelayer +│ ├── adapters/ # Datenquellen-Adapter (Mock, CMS, etc.) +│ ├── config/ # Konfiguration +│ ├── monitoring/ # Logging, Metrics, Tracing +│ ├── plugins/ # Apollo Server Plugins +│ ├── types/ # TypeScript Typen +│ ├── utils/ # Utilities (Cache, Dataloader, etc.) +│ ├── index.ts # Server Entry Point +│ ├── schema.ts # GraphQL Schema +│ └── resolvers.ts # GraphQL Resolvers +│ +├── src/ # Astro Frontend +│ ├── components/ # Astro Komponenten +│ ├── layouts/ # Layout Templates +│ ├── lib/ # Utilities & GraphQL Client +│ ├── pages/ # Astro Pages +│ └── styles/ # Global Styles +│ +└── docs/ # Dokumentation +``` + +## ⚙️ Konfiguration + +### Environment Variables + +Erstelle eine `.env` Datei im Root-Verzeichnis: + +```env +# Server Ports +PORT=4000 # GraphQL Server Port +METRICS_PORT=9090 # Metrics Server Port + +# Logging +LOG_LEVEL=info # debug, info, warn, error +NODE_ENV=development # development, production + +# GraphQL +MAX_QUERY_COMPLEXITY=1000 # Query Complexity Limit + +# Redis (Optional) +REDIS_ENABLED=true # Redis Cache aktivieren +REDIS_HOST=localhost # Redis Host +REDIS_PORT=6379 # Redis Port +REDIS_PASSWORD= # Optional: Redis Password + +# Cache TTLs (in Millisekunden) +CACHE_PAGES_TTL=60000 # 60 Sekunden +CACHE_PAGE_SEO_TTL=300000 # 5 Minuten +CACHE_NAVIGATION_TTL=300000 # 5 Minuten +CACHE_PRODUCTS_TTL=30000 # 30 Sekunden +``` + +### Cache-Konfiguration + +Cache-TTLs können über Environment Variables oder direkt in `middlelayer/config/cache.ts` konfiguriert werden. + +## 🌐 Endpoints + +| Service | URL | Beschreibung | +|---------|-----|--------------| +| GraphQL API | `http://localhost:4000` | GraphQL Endpoint & Playground | +| Metrics | `http://localhost:9090/metrics` | Prometheus Metrics | +| Health Check | `http://localhost:9090/health` | Service Health Status | +| Astro Frontend | `http://localhost:4321` | Frontend Application | + +## 📊 GraphQL Schema + +### Queries + +```graphql +# Produkte +query { + products(limit: 4) { + id + name + price + originalPrice + promotion { + category + text + } + } + + product(id: "prod-123") { + id + name + description + price + } +} + +# CMS (mit Locale-Support) +query { + pageSeo(locale: "de") { + title + description + } + + page(slug: "/about", locale: "de") { + headline + subheadline + } + + pages(locale: "en") { + slug + name + } + + navigation(locale: "de") { + links { + name + slug + } + } +} + +# Übersetzungen +query { + translations(locale: "de", namespace: "auth") { + locale + translations { + key + value + } + } +} + } + + page(slug: "/") { + slug + name + headline + } + + navigation { + name + links { + name + url + } + } +} +``` + +## 🏗️ Architektur + +### Middlelayer + +Der Middlelayer verwendet das **Adapter Pattern** für flexible Datenquellen: + +``` +Frontend (Astro) + ↓ +GraphQL API (Apollo Server) + ↓ +DataService (Singleton) + ↓ +DataAdapter (Interface) + ↓ +┌─────────────┬──────────────┬─────────────┐ +│ Mock Adapter│ CMS Adapter │ DB Adapter │ +└─────────────┴──────────────┴─────────────┘ +``` + +**Vorteile:** +- Einfaches Wechseln zwischen Datenquellen +- Testbarkeit durch Mock-Adapter +- Zentrale Logik im DataService +- Caching auf Service-Ebene + +### Frontend + +- **Astro** für Server-Side Rendering +- **Alpine.js** für Client-Side Interaktivität +- **Tailwind CSS** für Styling +- **GraphQL Client** für Datenabfragen + +## 🔧 Entwicklung + +### NPM Scripts + +```bash +npm run mock:server # Startet GraphQL Server +npm run dev # Startet Astro Dev Server +npm start # Startet beide (concurrently) +npm run build # Production Build +npm run preview # Preview Production Build +``` + +### Neuen Adapter hinzufügen + +1. Implementiere `DataAdapter` Interface: +```typescript +// middlelayer/adapters/myAdapter.ts +import type { DataAdapter } from './interface.js'; + +export class MyAdapter implements DataAdapter { + async getProducts(limit?: number): Promise { + // Implementierung + } + // ... weitere Methoden +} +``` + +2. In `middlelayer/adapters/config.ts` registrieren: +```typescript +export function createAdapter(): DataAdapter { + const adapterType = process.env.ADAPTER_TYPE || 'mock'; + + if (adapterType === 'myAdapter') { + return new MyAdapter(); + } + // ... +} +``` + +## 📈 Monitoring + +### Prometheus Metrics + +Metriken sind verfügbar unter `http://localhost:9090/metrics`: + +- `graphql_queries_total` - Anzahl der Queries +- `graphql_query_duration_seconds` - Query-Dauer +- `cache_hits_total` / `cache_misses_total` - Cache-Statistiken +- `dataservice_calls_total` - DataService-Aufrufe +- `errors_total` - Fehler-Anzahl + +### Logging + +Structured Logging mit Winston: +- Console Output (Development) +- JSON Logs (Production) +- Log-Level konfigurierbar + +### Tracing + +Automatische Trace-ID-Generierung für Request-Tracking. + +## 🚀 Deployment + +### Production Build + +```bash +npm run build +``` + +### Docker (Beispiel) + +```dockerfile +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY . . +RUN npm run build +CMD ["npm", "start"] +``` + +## 🧪 Testing + +```bash +# GraphQL Query testen +curl -X POST http://localhost:4000 \ + -H "Content-Type: application/json" \ + -d '{"query":"{ products(limit: 4) { id name price } }"}' + +# Health Check +curl http://localhost:9090/health + +# Metrics +curl http://localhost:9090/metrics +``` + +## 📚 Weitere Dokumentation + +- [Architektur & Skalierung](./docs/ARCHITECTURE_SCALING.md) - Detaillierte Architektur-Analyse +- [Redis Setup](./middlelayer/utils/REDIS_SETUP.md) - Redis Cache Konfiguration +- [Monitoring](./middlelayer/monitoring/README.md) - Monitoring & Observability + +## 🐛 Bekannte Probleme + +### Query Complexity Schema-Realm-Konflikt + +**Problem:** `graphql-query-complexity` hat einen Schema-Realm-Konflikt mit Apollo Server. + +**Workaround:** Das Plugin überspringt den Check bei Realm-Konflikten automatisch. Funktioniert, aber Complexity-Check wird manchmal übersprungen. + +**Status:** Funktioniert, permanente Lösung in Arbeit. + +## 🌍 Internationalization (i18n) + +### URL-basierte Locales + +Das System unterstützt URL-basierte Locales: +- `/de` - Deutsche Version +- `/en` - Englische Version +- `/` - Auto-Redirect zu Browser-Locale oder Cookie + +### Übersetzungen + +**Architektur:** +- **Defaults**: Fallback-Übersetzungen in `src/lib/i18n/defaults.ts` +- **Middlelayer**: Übersetzungen können vom GraphQL-Server geladen werden +- **Überschreibungen**: Middlelayer-Übersetzungen überschreiben Defaults + +**Verwendung in React:** +```tsx +import { useI18n } from "../lib/i18n/useI18n.js"; + +function MyComponent() { + const { t } = useI18n("auth"); + return

{t("login.title")}

; +} +``` + +**Verwendung in Alpine.js:** +```html +
+ +
+``` + +### CMS-Locale-Support + +**Contentful-Ansatz:** +Contentful verwendet ein Locale-System, bei dem: +- Jedes Content-Feld lokalisiert werden kann +- Standard-Locale wird definiert (z.B. `en-US`) +- Fallback-Locales können konfiguriert werden +- API-Aufrufe enthalten `locale`-Parameter: `fields.headline['en-US']` + +**Unser System:** +- GraphQL-Schema unterstützt `locale`-Parameter für alle CMS-Queries +- Adapter-Interface ist vorbereitet für Locale-Parameter +- Mock-Adapter gibt aktuell alle Locales gleich zurück (TODO: Locale-spezifische Mock-Daten) +- Bei Contentful-Integration: Adapter würde `locale`-Parameter an Contentful API weitergeben + +**Beispiel GraphQL Query:** +```graphql +query { + page(slug: "/about", locale: "de") { + headline # Deutsche Übersetzung + subheadline + } + + page(slug: "/about", locale: "en") { + headline # Englische Übersetzung + subheadline + } +} +``` + +## 🔮 Roadmap + +- [ ] Query Complexity Schema-Realm-Problem dauerhaft lösen +- [ ] Database Adapter (PostgreSQL, MySQL) +- [ ] Headless CMS Adapter (Contentful, Strapi) + - [ ] Locale-spezifische Inhalte implementieren + - [ ] Fallback-Locales konfigurieren +- [ ] Rate Limiting +- [ ] Authentication/Authorization ✅ (implementiert) +- [ ] GraphQL Subscriptions +- [ ] i18n: Weitere Sprachen hinzufügen +- [ ] i18n: Locale-spezifische Mock-Daten für CMS +- [ ] E2E Tests + +## 🤝 Beitragen + +1. Fork das Repository +2. Erstelle einen Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Committe deine Änderungen (`git commit -m 'Add some AmazingFeature'`) +4. Push zum Branch (`git push origin feature/AmazingFeature`) +5. Öffne einen Pull Request + +## 📝 Lizenz + +Dieses Projekt ist privat. + +## 👥 Autoren + +- Entwickelt mit ❤️ für moderne E-Commerce-Lösungen + +--- + +**Hinweis:** Dieses Projekt ist in aktiver Entwicklung. Für Fragen oder Probleme öffne bitte ein Issue. diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..276ae12 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,29 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import tailwind from "@tailwindcss/vite"; +import react from "@astrojs/react"; +import alpinejs from "@astrojs/alpinejs"; + +// https://astro.build/config +import { fileURLToPath } from "url"; +import { resolve } from "path"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +export default defineConfig({ + vite: { + plugins: [tailwind()], + resolve: { + alias: { + "@middlelayer-types": resolve(__dirname, "./middlelayer/types"), + "@middlelayer": resolve(__dirname, "./middlelayer"), + }, + }, + }, + + integrations: [react(), alpinejs()], + + // URL-basierte Locale-Routing + output: "server", + adapter: undefined, // Für SSR (später kann ein Adapter hinzugefügt werden) +}); diff --git a/docs/ARCHITECTURE_SCALING.md b/docs/ARCHITECTURE_SCALING.md new file mode 100644 index 0000000..6c6f35c --- /dev/null +++ b/docs/ARCHITECTURE_SCALING.md @@ -0,0 +1,395 @@ +# Architektur-Analyse: Skalierung für großen Onlineshop + +## Aktuelle Architektur - Stärken ✅ + +1. **Adapter Pattern** - Gute Abstraktion für Datenquellen +2. **Separation of Concerns** - Klare Trennung zwischen GraphQL, DataService und Adaptern +3. **Type Safety** - TypeScript durchgängig verwendet +4. **Caching-Layer** - Grundlegende Caching-Strategie vorhanden +5. **Error Handling** - Strukturierte Fehlerbehandlung + +## Kritische Verbesserungen für hohen Traffic 🚨 + +### 1. **Caching-Strategie** + +**Problem:** +- In-Memory Cache ist pro Server-Instanz isoliert +- Cache geht bei Neustart verloren +- Keine Cache-Invalidierung bei Updates +- Keine Cache-Warming-Strategie + +**Lösung:** +```typescript +// Redis-basierter Cache mit Clustering +import Redis from 'ioredis'; + +class RedisCache { + private client: Redis; + private cluster: Redis.Cluster; + + // Cache-Tags für gezielte Invalidierung + async invalidateByTag(tag: string) { ... } + + // Cache-Warming beim Start + async warmCache() { ... } +} +``` + +**Empfehlungen:** +- ✅ Redis Cluster für verteilten Cache +- ✅ Cache-Tags für gezielte Invalidierung (z.B. `product:123`, `category:electronics`) +- ✅ Cache-Warming beim Deployment +- ✅ Stale-While-Revalidate Pattern +- ✅ CDN für statische Assets (Bilder, CSS, JS) + +### 2. **Database Connection Pooling** + +**Problem:** +- Keine Connection Pooling sichtbar +- Risiko von Connection Exhaustion bei hohem Traffic + +**Lösung:** +```typescript +// Connection Pool für Datenbank-Adapter +class DatabaseAdapter implements DataAdapter { + private pool: Pool; + + constructor() { + this.pool = new Pool({ + max: 20, // Max Connections + min: 5, // Min Connections + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }); + } +} +``` + +**Empfehlungen:** +- ✅ Connection Pooling (PostgreSQL, MySQL) +- ✅ Read Replicas für Read-Heavy Operations +- ✅ Database Query Optimization (Indizes, Query-Analyse) +- ✅ Connection Monitoring & Alerting + +### 3. **GraphQL Performance** + +**Problem:** +- Keine Query Complexity Limits +- Keine Dataloader für N+1 Queries +- Keine Query Caching +- Keine Rate Limiting + +**Lösung:** +```typescript +// Apollo Server mit Performance-Features +const server = new ApolloServer({ + typeDefs, + resolvers, + plugins: [ + // Query Complexity + { + requestDidStart() { + return { + didResolveOperation({ request, operation }) { + const complexity = calculateComplexity(operation); + if (complexity > 1000) { + throw new Error('Query too complex'); + } + }, + }; + }, + }, + // Response Caching + responseCachePlugin({ + sessionId: (requestContext) => + requestContext.request.http?.headers.get('session-id') ?? null, + }), + // Rate Limiting + rateLimitPlugin({ + identifyContext: (ctx) => ctx.request.http?.headers.get('x-user-id'), + }), + ], +}); +``` + +**Empfehlungen:** +- ✅ Query Complexity Limits +- ✅ Dataloader für Batch-Loading +- ✅ Response Caching (Apollo Server) +- ✅ Rate Limiting (pro User/IP) +- ✅ Query Persisted Queries +- ✅ GraphQL Query Analysis & Monitoring + +### 4. **Load Balancing & Horizontal Scaling** + +**Problem:** +- Single Server Instance +- Keine Load Balancing +- Keine Health Checks + +**Lösung:** +```yaml +# Docker Compose / Kubernetes +services: + graphql: + replicas: 5 + healthcheck: + path: /health + interval: 10s + redis: + cluster: true + database: + read-replicas: 3 +``` + +**Empfehlungen:** +- ✅ Kubernetes / Docker Swarm für Orchestrierung +- ✅ Load Balancer (NGINX, HAProxy, AWS ALB) +- ✅ Health Check Endpoints +- ✅ Auto-Scaling basierend auf CPU/Memory +- ✅ Blue-Green Deployments + +### 5. **Monitoring & Observability** + +**Problem:** +- Nur Console-Logging +- Keine Metriken +- Keine Distributed Tracing + +**Lösung:** +```typescript +// Structured Logging + Metrics +import { createLogger } from 'winston'; +import { PrometheusMetrics } from './metrics'; + +const logger = createLogger({ + format: winston.format.json(), + transports: [ + new winston.transports.Console(), + new winston.transports.File({ filename: 'error.log' }), + ], +}); + +const metrics = new PrometheusMetrics(); + +// In Resolvers +async getProducts(limit: number) { + const start = Date.now(); + try { + const products = await dataService.getProducts(limit); + metrics.recordQueryDuration('getProducts', Date.now() - start); + metrics.incrementQueryCount('getProducts', 'success'); + return products; + } catch (error) { + metrics.incrementQueryCount('getProducts', 'error'); + logger.error('Failed to get products', { error, limit }); + throw error; + } +} +``` + +**Empfehlungen:** +- ✅ Structured Logging (Winston, Pino) +- ✅ Metrics (Prometheus + Grafana) +- ✅ Distributed Tracing (Jaeger, Zipkin) +- ✅ APM (Application Performance Monitoring) +- ✅ Error Tracking (Sentry, Rollbar) +- ✅ Real-time Dashboards + +### 6. **Security** + +**Problem:** +- Keine Authentication/Authorization +- Keine Input Validation +- Keine CORS-Konfiguration +- Keine Rate Limiting + +**Lösung:** +```typescript +// Security Middleware +import { rateLimit } from 'express-rate-limit'; +import helmet from 'helmet'; +import { validate } from 'graphql-validate'; + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // Limit each IP to 100 requests per windowMs +}); + +// GraphQL Input Validation +const validateInput = (schema, input) => { + const errors = validate(schema, input); + if (errors.length > 0) { + throw new ValidationError(errors); + } +}; +``` + +**Empfehlungen:** +- ✅ Authentication (JWT, OAuth) +- ✅ Authorization (Role-Based Access Control) +- ✅ Input Validation (Zod, Yup) +- ✅ Rate Limiting (pro Endpoint/User) +- ✅ CORS-Konfiguration +- ✅ SQL Injection Prevention (Parameterized Queries) +- ✅ XSS Protection +- ✅ CSRF Protection +- ✅ Security Headers (Helmet.js) + +### 7. **Database Optimierungen** + +**Problem:** +- Keine Indizes sichtbar +- Keine Query-Optimierung +- Keine Pagination für große Datensätze + +**Lösung:** +```typescript +// Optimierte Queries mit Pagination +async getProducts(limit: number, offset: number, filters?: ProductFilters) { + // Indexed Query + const query = ` + SELECT * FROM products + WHERE category = $1 + ORDER BY created_at DESC + LIMIT $2 OFFSET $3 + `; + + // Mit Indizes: + // CREATE INDEX idx_products_category ON products(category); + // CREATE INDEX idx_products_created_at ON products(created_at); +} +``` + +**Empfehlungen:** +- ✅ Database Indizes für häufige Queries +- ✅ Pagination (Cursor-based für große Datensätze) +- ✅ Query Optimization (EXPLAIN ANALYZE) +- ✅ Database Sharding für sehr große Datenmengen +- ✅ Read Replicas für Read-Heavy Workloads +- ✅ Materialized Views für komplexe Aggregationen + +### 8. **Error Handling & Resilience** + +**Problem:** +- Keine Retry-Logik +- Keine Circuit Breaker +- Keine Fallback-Strategien + +**Lösung:** +```typescript +// Circuit Breaker Pattern +import { CircuitBreaker } from 'opossum'; + +const breaker = new CircuitBreaker(dataService.getProducts, { + timeout: 3000, + errorThresholdPercentage: 50, + resetTimeout: 30000, +}); + +// Retry mit Exponential Backoff +async function withRetry( + fn: () => Promise, + maxRetries = 3 +): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) throw error; + await sleep(2 ** i * 1000); // Exponential backoff + } + } +} +``` + +**Empfehlungen:** +- ✅ Circuit Breaker Pattern +- ✅ Retry mit Exponential Backoff +- ✅ Fallback zu Cache bei DB-Fehlern +- ✅ Graceful Degradation +- ✅ Bulkhead Pattern (Isolation von Ressourcen) + +### 9. **API Versioning & Backward Compatibility** + +**Problem:** +- Keine API-Versionierung +- Breaking Changes könnten Frontend brechen + +**Lösung:** +```typescript +// GraphQL Schema Versioning +const typeDefsV1 = `...`; +const typeDefsV2 = `...`; + +const server = new ApolloServer({ + typeDefs: [typeDefsV1, typeDefsV2], + resolvers: { + Query: { + productsV1: resolvers.products, + productsV2: resolvers.productsV2, + }, + }, +}); +``` + +**Empfehlungen:** +- ✅ GraphQL Schema Versioning +- ✅ Deprecation Warnings +- ✅ Feature Flags für neue Features +- ✅ Backward Compatibility Tests + +### 10. **Deployment & CI/CD** + +**Empfehlungen:** +- ✅ Automated Testing (Unit, Integration, E2E) +- ✅ CI/CD Pipeline (GitHub Actions, GitLab CI) +- ✅ Blue-Green Deployments +- ✅ Canary Releases +- ✅ Database Migrations (automatisiert) +- ✅ Rollback-Strategien + +## Priorisierte Roadmap 🗺️ + +### Phase 1: Foundation (Woche 1-2) +1. ✅ Redis Cache Integration +2. ✅ Database Connection Pooling +3. ✅ Structured Logging +4. ✅ Basic Monitoring (Prometheus) + +### Phase 2: Performance (Woche 3-4) +1. ✅ Dataloader für N+1 Queries +2. ✅ Query Complexity Limits +3. ✅ Response Caching +4. ✅ Database Indizes + +### Phase 3: Resilience (Woche 5-6) +1. ✅ Circuit Breaker +2. ✅ Retry Logic +3. ✅ Health Checks +4. ✅ Rate Limiting + +### Phase 4: Scale (Woche 7-8) +1. ✅ Load Balancing +2. ✅ Horizontal Scaling (Kubernetes) +3. ✅ Read Replicas +4. ✅ CDN Integration + +### Phase 5: Advanced (Woche 9+) +1. ✅ Distributed Tracing +2. ✅ Advanced Monitoring +3. ✅ Auto-Scaling +4. ✅ Database Sharding (falls nötig) + +## Fazit + +Die aktuelle Architektur ist **gut strukturiert** und bietet eine **solide Basis**. Für einen **großen Onlineshop mit hohem Traffic** müssen jedoch folgende Bereiche priorisiert werden: + +1. **Caching** (Redis) - Höchste Priorität +2. **Database Optimierung** - Kritisch für Performance +3. **Monitoring** - Essentiell für Operations +4. **Horizontal Scaling** - Notwendig für Wachstum +5. **Resilience Patterns** - Wichtig für Verfügbarkeit + +Mit diesen Verbesserungen kann die Architektur **tausende von Requests pro Sekunde** handhaben. + diff --git a/middlelayer/IMPROVEMENTS.md b/middlelayer/IMPROVEMENTS.md new file mode 100644 index 0000000..07740c2 --- /dev/null +++ b/middlelayer/IMPROVEMENTS.md @@ -0,0 +1,133 @@ +# Middlelayer Verbesserungsvorschläge + +## 🔴 Kritisch / Wichtig + +### 1. **Redundante `mapLayout` Methode entfernen** +**Problem:** `mapLayout` gibt nur den Parameter zurück - komplett redundant +**Lösung:** Direkt `layout` verwenden statt `this.mapLayout(layout)` + +**Datei:** `mappers/pageMapper.ts:32-36` + +### 2. **console.error durch logger ersetzen** +**Problem:** Inkonsistente Logging - manche Stellen nutzen `console.error` statt `logger` +**Lösung:** Alle `console.error/warn` durch `logger.error/warn` ersetzen + +**Dateien:** +- `resolvers.ts:16,19` +- `adapters/config.ts:18` +- `plugins/queryComplexity.ts:56` + +### 3. **Error Handling in Resolvers vereinfachen** +**Problem:** Wiederholende try-catch Blöcke in jedem Resolver +**Lösung:** Wrapper-Funktion für Resolver erstellen + +## 🟡 Wichtig / Code-Qualität + +### 4. **PageMapper: Strategy Pattern statt if-Statements** +**Problem:** 8 if-Statements in `mapContentItem` - schwer wartbar +**Lösung:** Map-basiertes Strategy Pattern + +```typescript +private static contentMappers = new Map ContentItem>([ + [ContentType.html, this.mapHtml], + [ContentType.markdown, this.mapMarkdown], + // ... +]); +``` + +### 5. **DataService: Code-Duplikation reduzieren** +**Problem:** Wiederholende Cache + Metrics Logik +**Lösung:** Helper-Methode `withCacheAndMetrics` erstellen + +### 6. **Cache Keys zentralisieren** +**Problem:** Cache Keys werden überall manuell erstellt +**Lösung:** `CacheKeyBuilder` Utility-Klasse + +```typescript +class CacheKeyBuilder { + static page(slug: string, locale?: string) { + return `page:${slug}:${locale || "default"}`; + } + // ... +} +``` + +### 7. **Type Safety verbessern** +**Problem:** Einige `any` Types (z.B. `page` Loader) +**Lösung:** ✅ Bereits behoben in `dataloaders.ts` + +## 🟢 Nice-to-Have / Refactoring + +### 8. **__cms Verzeichnis dokumentieren/archivieren** +**Problem:** Alte Contentful-Typen mit `Contentful_` Präfix - werden nicht mehr verwendet +**Lösung:** +- README.md hinzufügen: "Legacy - nicht mehr verwendet" +- Oder in `_legacy/` verschieben + +### 9. **Resolver Wrapper für Error Handling** +**Lösung:** +```typescript +function withErrorHandling( + resolver: () => Promise +): Promise { + try { + return await resolver(); + } catch (error) { + handleError(error); + } +} +``` + +### 10. **DataService: Metrics-Tracking vereinheitlichen** +**Problem:** Nur `getPage` und `getProducts` haben Metrics, andere nicht +**Lösung:** Alle Methoden mit Metrics versehen oder Helper-Methode + +### 11. **ContentEntry Union Type verbessern** +**Problem:** Type Guards könnten besser sein +**Lösung:** Type Guard Functions für jeden Content-Type + +### 12. **Dokumentation erweitern** +- JSDoc Kommentare für alle öffentlichen Methoden +- Beispiele für Adapter-Implementierung +- Performance-Best-Practices + +## 📊 Priorisierung + +1. **Sofort:** #1, #2 (Redundanz entfernen, Logging konsistent) ✅ +2. **Bald:** #3, #4 (Code-Qualität verbessern) ✅ +3. **Später:** #5, #6, #7 (Refactoring für Wartbarkeit) ✅ +4. **Optional:** #8-12 (Nice-to-Have) + +## ✅ Umgesetzte Verbesserungen + +### ✅ 1. Redundante `mapLayout` Methode entfernt +- Entfernt und direkt `layout` verwendet + +### ✅ 2. console.error durch logger ersetzt +- Alle `console.error` durch `logger.error` ersetzt + +### ✅ 3. Error Handling in Resolvers vereinfacht +- `withErrorHandling` Wrapper erstellt +- Alle Query- und Mutation-Resolvers verwenden den Wrapper + +### ✅ 4. PageMapper: Strategy Pattern +- Map-basiertes Strategy Pattern implementiert +- 8 if-Statements durch wartbare Map ersetzt + +### ✅ 5. DataService: Code-Duplikation reduziert +- `DataServiceHelpers` Klasse erstellt +- `withCacheAndMetrics` und `withCache` Methoden +- Alle DataService-Methoden vereinfacht + +### ✅ 6. Cache Keys zentralisiert +- `CacheKeyBuilder` Utility-Klasse erstellt +- Alle Cache-Keys an einem Ort + +### ✅ 7. Type Safety verbessert +- `ContentItem.__resolveType` verwendet jetzt `ContentItem` statt `any` +- Map-basiertes Type-Resolution statt if-Statements + +### ✅ 8. Mutation Resolver vereinfacht +- `register` und `login` verwenden jetzt auch `withErrorHandling` +- Konsistentes Error Handling überall + diff --git a/middlelayer/__cms/Contentful_Badges.ts b/middlelayer/__cms/Contentful_Badges.ts new file mode 100644 index 0000000..e7be81f --- /dev/null +++ b/middlelayer/__cms/Contentful_Badges.ts @@ -0,0 +1,14 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_ComponentListSkeleton } from "src/@types/Contentful_List"; + +export interface CF_ComponentBadges { + internal: string; + badges: CF_ComponentListSkeleton; + variants: "light" | "dark"; + layout?: any; +} + +export interface CF_ComponentBadgesSkeleton { + contentTypeId: CF_ContentType.badges + fields: CF_ComponentBadges +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Campaign.ts b/middlelayer/__cms/Contentful_Campaign.ts new file mode 100644 index 0000000..3240f09 --- /dev/null +++ b/middlelayer/__cms/Contentful_Campaign.ts @@ -0,0 +1,24 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { Asset } from "contentful"; + +export interface CF_Campaign { + campaignName: string; + urlPatter: string; + selector: string; + insertHtml: + | "afterbegin" + | "beforeend" + | "afterend" + | "beforebegin" + | "replace"; + timeUntil?: string; + javascript?: string; + medias?: Asset[]; + html?: string; + css?: string; +} + +export interface CF_CampaignSkeleton { + contentTypeId: CF_ContentType.campaign; + fields: CF_Campaign; +} diff --git a/middlelayer/__cms/Contentful_Campaigns.ts b/middlelayer/__cms/Contentful_Campaigns.ts new file mode 100644 index 0000000..2b115b3 --- /dev/null +++ b/middlelayer/__cms/Contentful_Campaigns.ts @@ -0,0 +1,13 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_CampaignSkeleton } from "./Contentful_Campaign"; + +export interface CF_Campaigns { + id: string; + campaings: CF_CampaignSkeleton[]; + enable: boolean; +} + +export interface CF_CampaignsSkeleton { + contentTypeId: CF_ContentType.campaigns; + fields: CF_Campaigns; +} diff --git a/middlelayer/__cms/Contentful_CloudinaryImage.ts b/middlelayer/__cms/Contentful_CloudinaryImage.ts new file mode 100644 index 0000000..23ea076 --- /dev/null +++ b/middlelayer/__cms/Contentful_CloudinaryImage.ts @@ -0,0 +1,15 @@ +export interface CF_CloudinaryImage { + bytes: number; + created_at: string; + format: string; + height: number; + original_secure_url: string; + original_url: string; + public_id: string; + resource_type: string; + secure_url: string; + type: string; + url: string; + version: number; + width: number; +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Content.ts b/middlelayer/__cms/Contentful_Content.ts new file mode 100644 index 0000000..1f4b0ce --- /dev/null +++ b/middlelayer/__cms/Contentful_Content.ts @@ -0,0 +1,24 @@ +import type { EntrySkeletonType } from "contentful"; + +export type rowJutify = + | "start" + | "end" + | "center" + | "between" + | "around" + | "evenly"; +export type rowAlignItems = "start" | "end" | "center" | "baseline" | "stretch"; + +export interface CF_Content { + row1JustifyContent: rowJutify; + row1AlignItems: rowAlignItems; + row1Content: EntrySkeletonType[]; + + row2JustifyContent: rowJutify; + row2AlignItems: rowAlignItems; + row2Content: EntrySkeletonType[]; + + row3JustifyContent: rowJutify; + row3AlignItems: rowAlignItems; + row3Content: EntrySkeletonType[]; +} diff --git a/middlelayer/__cms/Contentful_ContentType.enum.ts b/middlelayer/__cms/Contentful_ContentType.enum.ts new file mode 100644 index 0000000..64646fa --- /dev/null +++ b/middlelayer/__cms/Contentful_ContentType.enum.ts @@ -0,0 +1,31 @@ +export enum CF_ContentType { + "componentLinkList" = "componentLinkList", + "badges" = "badges", + "componentPostOverview" = "componentPostOverview", + "footer" = "footer", + "fullwidthBanner" = "fullwidthBanner", + "headline" = "headline", + "html" = "html", + "image" = "image", + "img" = "img", + "iframe" = "iframe", + "imgGallery" = "imageGallery", + "internalReference" = "internalComponent", + "link" = "link", + "list" = "list", + "markdown" = "markdown", + "navigation" = "navigation", + "page" = "page", + "pageConfig" = "pageConfig", + "picture" = "picture", + "post" = "post", + "postComponent" = "postComponent", + "quote" = "quoteComponent", + "richtext" = "richtext", + "row" = "row", + "rowLayout" = "rowLayout", + "tag" = "tag", + "youtubeVideo" = "youtubeVideo", + "campaign" = "campaign", + "campaigns" = "campaigns", +} diff --git a/middlelayer/__cms/Contentful_Footer.ts b/middlelayer/__cms/Contentful_Footer.ts new file mode 100644 index 0000000..f57ba6b --- /dev/null +++ b/middlelayer/__cms/Contentful_Footer.ts @@ -0,0 +1,10 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_Content } from "./Contentful_Content"; + +export interface CF_Footer extends CF_Content { + id : string; +} +export type CF_FooterSkeleton = { + contentTypeId: CF_ContentType.footer + fields: CF_Footer +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_FullwidthBanner.ts b/middlelayer/__cms/Contentful_FullwidthBanner.ts new file mode 100644 index 0000000..7bfc935 --- /dev/null +++ b/middlelayer/__cms/Contentful_FullwidthBanner.ts @@ -0,0 +1,24 @@ +import type { CF_ComponentImgSkeleton } from "./Contentful_Img"; +import type { CF_CloudinaryImage } from "./Contentful_CloudinaryImage"; +import type { CF_ContentType } from "./Contentful_ContentType.enum"; + + +export enum CF_FullwidthBannerVariant { + "dark"= "dark", + "light" = "light" +} + +export interface CF_FullwidthBanner { + name: string, + variant : CF_FullwidthBannerVariant, + headline : string, + subheadline: string, + text : string, + image: CF_CloudinaryImage[]; + img: CF_ComponentImgSkeleton; +} + +export type CF_FullwidthBannerSkeleton = { + contentTypeId: CF_ContentType.fullwidthBanner + fields: CF_FullwidthBanner +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Grid.ts b/middlelayer/__cms/Contentful_Grid.ts new file mode 100644 index 0000000..efbdf66 --- /dev/null +++ b/middlelayer/__cms/Contentful_Grid.ts @@ -0,0 +1,29 @@ +import type { EntrySkeletonType } from "contentful"; +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; + +export type CF_justfyContent = "start" | "end" | "center" | "between" | "around" | "evenly"; +export type CF_alignItems = "start" | "end" | "center" | "baseline" | "stretch" + +export interface CF_Column_Alignment { + justifyContent: CF_justfyContent, + alignItems: CF_alignItems +} + +export interface CF_Column_Layout { + layoutMobile: T + layoutTablet: T + layoutDesktop: T +} + +export type CF_Row_1_Column_Layout = "auto" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12"; + +export interface CF_Row { + alignment: EntrySkeletonType + layout: EntrySkeletonType> + content: EntrySkeletonType[] +} + +export interface CF_RowSkeleton { + contentTypeId: CF_ContentType.row + fields: CF_Row +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Headline.ts b/middlelayer/__cms/Contentful_Headline.ts new file mode 100644 index 0000000..f8d5451 --- /dev/null +++ b/middlelayer/__cms/Contentful_Headline.ts @@ -0,0 +1,21 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum.js"; +import type { CF_ComponentLayout } from "./Contentful_Layout.js"; + +export type CF_Component_Headline_Align = "left" | "center" | "right"; + +export type CF_Component_Headline_Tag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + +export type CF_alignTextClasses = "text-left" | "text-center" | "text-right"; + +export interface CF_ComponentHeadline { + internal: string; + text: string; + tag: CF_Component_Headline_Tag; + layout: CF_ComponentLayout; + align?: CF_Component_Headline_Align; +} + +export interface CF_ComponentHeadlineSkeleton { + contentTypeId: CF_ContentType.headline; + fields: CF_ComponentHeadline; +} diff --git a/middlelayer/__cms/Contentful_Html.ts b/middlelayer/__cms/Contentful_Html.ts new file mode 100644 index 0000000..34ba4ea --- /dev/null +++ b/middlelayer/__cms/Contentful_Html.ts @@ -0,0 +1,13 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; + +export interface CF_HTML { + id: string; + html: string; + layout: CF_ComponentLayout; +} + +export type CF_HTMLSkeleton = { + contentTypeId: CF_ContentType.html; + fields: CF_HTML; +}; diff --git a/middlelayer/__cms/Contentful_Iframe.ts b/middlelayer/__cms/Contentful_Iframe.ts new file mode 100644 index 0000000..3bfd45e --- /dev/null +++ b/middlelayer/__cms/Contentful_Iframe.ts @@ -0,0 +1,16 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentImgSkeleton } from "./Contentful_Img"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; + +export interface CF_ComponentIframe { + name: string; + content: string; + iframe: string; + overlayImage?: CF_ComponentImgSkeleton; + layout: CF_ComponentLayout; +} + +export interface CF_ComponentIframeSkeleton { + contentTypeId: CF_ContentType.iframe; + fields: CF_ComponentIframe; +} diff --git a/middlelayer/__cms/Contentful_Image.ts b/middlelayer/__cms/Contentful_Image.ts new file mode 100644 index 0000000..9f76c45 --- /dev/null +++ b/middlelayer/__cms/Contentful_Image.ts @@ -0,0 +1,17 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentImgSkeleton } from "./Contentful_Img"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; + +export interface CF_ComponentImage { + name: string; + image: CF_ComponentImgSkeleton; + caption: string; + layout: CF_ComponentLayout; + maxWidth?: number; + aspectRatio?: number; +} + +export interface CF_ComponentImageSkeleton { + contentTypeId: CF_ContentType.image; + fields: CF_ComponentImage; +} diff --git a/middlelayer/__cms/Contentful_ImageGallery.ts b/middlelayer/__cms/Contentful_ImageGallery.ts new file mode 100644 index 0000000..d9f8e60 --- /dev/null +++ b/middlelayer/__cms/Contentful_ImageGallery.ts @@ -0,0 +1,15 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum.js"; +import type { CF_ComponentImgSkeleton } from "./Contentful_Img.js"; +import type { CF_ComponentLayout } from "./Contentful_Layout.js"; + +export interface CF_ImageGallery { + name: string; + images: CF_ComponentImgSkeleton[]; + layout: CF_ComponentLayout; + description?: string; +} + +export interface CF_ImageGallerySkeleton { + contentTypeId: CF_ContentType.imgGallery; + fields: CF_ImageGallery; +} diff --git a/middlelayer/__cms/Contentful_Img.ts b/middlelayer/__cms/Contentful_Img.ts new file mode 100644 index 0000000..faf16ef --- /dev/null +++ b/middlelayer/__cms/Contentful_Img.ts @@ -0,0 +1,26 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; + + +export interface CF_ComponentImgDetails { + size: number, + image: { + width: number, + height: number + } +} + +export interface CF_ComponentImg { + title: string; + description: string; + file: { + url: string; + details: CF_ComponentImgDetails; + fileName: string; + contentType: string; + } +} + +export interface CF_ComponentImgSkeleton { + contentTypeId: CF_ContentType.img + fields: CF_ComponentImg +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_InternalReference.ts b/middlelayer/__cms/Contentful_InternalReference.ts new file mode 100644 index 0000000..e292bbe --- /dev/null +++ b/middlelayer/__cms/Contentful_InternalReference.ts @@ -0,0 +1,14 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_ComponentLayout } from "src/@types/Contentful_Layout"; +import type { EntryFieldTypes } from "contentful"; + +export interface CF_internalReference { + data: EntryFieldTypes.Object, + reference: string, + layout: CF_ComponentLayout +} + +export type CF_internalReferenceSkeleton = { + contentTypeId: CF_ContentType.internalReference + fields: CF_internalReference +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Layout.ts b/middlelayer/__cms/Contentful_Layout.ts new file mode 100644 index 0000000..a9ec491 --- /dev/null +++ b/middlelayer/__cms/Contentful_Layout.ts @@ -0,0 +1,100 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; + +export type CF_Component_Layout_Width = + | "1" + | "2" + | "3" + | "4" + | "5" + | "6" + | "7" + | "8" + | "9" + | "10" + | "11" + | "12"; + +export type CF_widths_mobile = + | "w-full" + | "w-1/12" + | "w-2/12" + | "w-3/12" + | "w-4/12" + | "w-5/12" + | "w-6/12" + | "w-7/12" + | "w-8/12" + | "w-9/12" + | "w-10/12" + | "w-11/12"; + +export type CF_widths_tablet = + | "" + | "md:w-full" + | "md:w-1/12" + | "md:w-2/12" + | "md:w-3/12" + | "md:w-4/12" + | "md:w-5/12" + | "md:w-6/12" + | "md:w-7/12" + | "md:w-8/12" + | "md:w-9/12" + | "md:w-10/12" + | "md:w-11/12"; + +export type CF_widths_desktop = + | "" + | "lg:w-full" + | "lg:w-1/12" + | "lg:w-2/12" + | "lg:w-3/12" + | "lg:w-4/12" + | "lg:w-5/12" + | "lg:w-6/12" + | "lg:w-7/12" + | "lg:w-8/12" + | "lg:w-9/12" + | "lg:w-10/12" + | "lg:w-11/12"; + +export type CF_Component_Layout_Space = + | 0 + | .5 + | 1 + | 1.5 + | 2 + +export type CF_Component_Space = + | "" + | "mb-[0.5rem]" + | "mb-[1rem]" + | "mb-[1.5rem]" + | "mb-[2rem]" + +export type CF_justfyContent = + | "justify-start" + | "justify-end" + | "justify-center" + | "justify-between" + | "justify-around" + | "justify-evenly"; + +export type CF_alignItems = + | "items-start" + | "items-end" + | "items-center" + | "items-baseline" + | "items-stretch"; + +export interface CF_ComponentLayout { + mobile: CF_Component_Layout_Width; + tablet?: CF_Component_Layout_Width; + desktop?: CF_Component_Layout_Width; + spaceBottom?: CF_Component_Layout_Space +} + +export interface CF_ComponentLayoutSkeleton { + contentTypeId: CF_ContentType.rowLayout + fields: CF_ComponentLayout +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Link.ts b/middlelayer/__cms/Contentful_Link.ts new file mode 100644 index 0000000..50a405f --- /dev/null +++ b/middlelayer/__cms/Contentful_Link.ts @@ -0,0 +1,23 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; + +export interface CF_Link { + name: string; + internal: string; + linkName: string; + icon?: string; + color?: string; + url: string; + newTab?: boolean; + external?: boolean; + description?: string; + alt?: string; + showText?: boolean; + author: string; + date: string; + source: string; +} + +export interface CF_LinkSkeleton { + contentTypeId: CF_ContentType.link; + fields: CF_Link; +} diff --git a/middlelayer/__cms/Contentful_Link_List.ts b/middlelayer/__cms/Contentful_Link_List.ts new file mode 100644 index 0000000..030cbd2 --- /dev/null +++ b/middlelayer/__cms/Contentful_Link_List.ts @@ -0,0 +1,12 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_LinkSkeleton } from "./Contentful_Link"; + +export interface CF_Link_List { + headline: string; + links: CF_LinkSkeleton[]; +} + +export type CF_LinkListSkeleton = { + contentTypeId: CF_ContentType.componentLinkList; + fields: CF_Link_List; +}; diff --git a/middlelayer/__cms/Contentful_List.ts b/middlelayer/__cms/Contentful_List.ts new file mode 100644 index 0000000..c1096e6 --- /dev/null +++ b/middlelayer/__cms/Contentful_List.ts @@ -0,0 +1,11 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; + +export interface CF_ComponentList { + internal: string; + item: string[]; +} + +export interface CF_ComponentListSkeleton { + contentTypeId: CF_ContentType.list + fields: CF_ComponentList +} diff --git a/middlelayer/__cms/Contentful_Markdown.ts b/middlelayer/__cms/Contentful_Markdown.ts new file mode 100644 index 0000000..4ece569 --- /dev/null +++ b/middlelayer/__cms/Contentful_Markdown.ts @@ -0,0 +1,15 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; +import type { TextAlignment } from "./TextAlignment"; + +export interface CF_Markdown { + name: string; + content: string; + layout: CF_ComponentLayout; + alignment: TextAlignment; +} + +export type CF_MarkdownSkeleton = { + contentTypeId: CF_ContentType.markdown; + fields: CF_Markdown; +}; \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Names.enum.ts b/middlelayer/__cms/Contentful_Names.enum.ts new file mode 100644 index 0000000..e79812a --- /dev/null +++ b/middlelayer/__cms/Contentful_Names.enum.ts @@ -0,0 +1,17 @@ +export enum CF_Navigation_Keys { + "header" = "navigation-header", + "socialMedia" = "navigation-social-media", + "footer" = "navigation-footer", +} + +export enum CF_PageConfigKey { + "pageConfig" = "page-config", +} + +export enum CF_Footer_Keys { + "main" = "main", +} + +export enum CF_Campaigns_Keys { + "campaigns" = "campaigns", +} diff --git a/middlelayer/__cms/Contentful_Navigation.ts b/middlelayer/__cms/Contentful_Navigation.ts new file mode 100644 index 0000000..d32d4d2 --- /dev/null +++ b/middlelayer/__cms/Contentful_Navigation.ts @@ -0,0 +1,14 @@ +import type { CF_Link } from "./Contentful_Link"; +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_Page } from "./Contentful_Page"; + +export interface CF_Navigation { + name: string; + internal: string; + links: Array<{ fields: CF_Link | CF_Page }>; +} + +export type CF_NavigationSkeleton = { + contentTypeId: CF_ContentType.navigation; + fields: CF_Navigation; +}; diff --git a/middlelayer/__cms/Contentful_Page.ts b/middlelayer/__cms/Contentful_Page.ts new file mode 100644 index 0000000..6941ec2 --- /dev/null +++ b/middlelayer/__cms/Contentful_Page.ts @@ -0,0 +1,19 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_FullwidthBannerSkeleton } from "./Contentful_FullwidthBanner"; +import type { CF_Content } from "./Contentful_Content"; +import type { CF_SEO } from "./Contentful_SEO"; + +export interface CF_Page extends CF_Content, CF_SEO { + slug: string; + name: string; + linkName: string; + icon?: string; + headline: string; + subheadline: string; + topFullwidthBanner: CF_FullwidthBannerSkeleton; +} + +export type CF_PageSkeleton = { + contentTypeId: CF_ContentType.page; + fields: CF_Page; +}; diff --git a/middlelayer/__cms/Contentful_PageConfig.ts b/middlelayer/__cms/Contentful_PageConfig.ts new file mode 100644 index 0000000..34fee24 --- /dev/null +++ b/middlelayer/__cms/Contentful_PageConfig.ts @@ -0,0 +1,18 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentImgSkeleton } from "./Contentful_Img"; + +export interface CF_PageConfig { + logo: CF_ComponentImgSkeleton; + footerText1: string; + seoTitle: string; + seoDescription: string; + blogTagPageHeadline: string; + blogPostsPageHeadline: string; + blogPostsPageSubHeadline: string; + website: string; +} + +export type CF_PageConfigSkeleton = { + contentTypeId: CF_ContentType.pageConfig; + fields: CF_PageConfig; +}; diff --git a/middlelayer/__cms/Contentful_Page_Seo.ts b/middlelayer/__cms/Contentful_Page_Seo.ts new file mode 100644 index 0000000..4b32fb0 --- /dev/null +++ b/middlelayer/__cms/Contentful_Page_Seo.ts @@ -0,0 +1,7 @@ +export interface CF_Page_Seo { + name: "page-about-seo", + title: "about", + description: "about", + metaRobotsIndex: "index", + metaRobotsFollow: "follow" +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_Picture.ts b/middlelayer/__cms/Contentful_Picture.ts new file mode 100644 index 0000000..d8c0cb8 --- /dev/null +++ b/middlelayer/__cms/Contentful_Picture.ts @@ -0,0 +1,57 @@ +import type { EntryFieldTypes } from "contentful"; +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_CloudinaryImage } from "src/@types/Contentful_CloudinaryImage"; + + +export type CF_PictureWidths = 400 | 800 | 1200 | 1400 +export type CF_PictureFormats = "aviv" | "jpg" | "png" | "webp" +export type CF_PictureFit = "contain" | "cover" | "fill" | "inside" | "outside" +export type CF_PicturePosition = "top" + | "right top" + | "right" + | "right bottom" + | "bottom" + | "left bottom" + | "left" + | "left top" + | "north" + | "northeast" + | "east" + | "southeast" + | "south" + | "southwest" + | "west" + | "northwest" + | "center" + | "centre" + | "cover" + | "entropy" + | "attention" +export type CF_PictureAspectRatio = 'original' + | '32:9' + | '16:9' + | '5:4' + | '4:3' + | '3:2' + | '1:1' + | '2:3' + | '3:4' + | '4:5' + + +export interface CF_Picture { + name: EntryFieldTypes.Text; + image: CF_CloudinaryImage[]; + alt?: EntryFieldTypes.Text; + widths: Array; + aspectRatio: CF_PictureAspectRatio; + formats: CF_PictureFormats; + fit: CF_PictureFit; + position: CF_PicturePosition; + layout?: any; +} + +export type CF_PictureSkeleton = { + contentTypeId: CF_ContentType.picture + fields: CF_Picture +} diff --git a/middlelayer/__cms/Contentful_Post.ts b/middlelayer/__cms/Contentful_Post.ts new file mode 100644 index 0000000..c2dfcb5 --- /dev/null +++ b/middlelayer/__cms/Contentful_Post.ts @@ -0,0 +1,25 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_ComponentImgSkeleton } from "./Contentful_Img"; +import type { CF_Content } from "./Contentful_Content"; +import type { CF_SEO } from "./Contentful_SEO"; +import type { CF_TagSkeleton } from "./Contentful_Tag"; + +export interface CF_Post extends CF_Content, CF_SEO { + postImage: CF_ComponentImgSkeleton; + postTag: CF_TagSkeleton[]; + slug: string; + linkName: string; + icon?: string; + headline: string; + important: boolean; + created: string; + date?: string; + subheadline: string; + excerpt: string; + content: string; +} + +export type CF_PostEntrySkeleton = { + contentTypeId: CF_ContentType.post; + fields: CF_Post; +}; diff --git a/middlelayer/__cms/Contentful_Post_Component.ts b/middlelayer/__cms/Contentful_Post_Component.ts new file mode 100644 index 0000000..e2bdd98 --- /dev/null +++ b/middlelayer/__cms/Contentful_Post_Component.ts @@ -0,0 +1,14 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; +import type { CF_PostEntrySkeleton } from "./Contentful_Post"; + +export interface CF_PostComponent { + id: string; + post: CF_PostEntrySkeleton; + layout: CF_ComponentLayout; +} + +export interface CF_PostComponentSkeleton { + contentTypeId: CF_ContentType.postComponent; + fields: CF_PostComponent; +} diff --git a/middlelayer/__cms/Contentful_Post_Overview.ts b/middlelayer/__cms/Contentful_Post_Overview.ts new file mode 100644 index 0000000..4e39643 --- /dev/null +++ b/middlelayer/__cms/Contentful_Post_Overview.ts @@ -0,0 +1,24 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; +import type { CF_Content } from "./Contentful_Content"; +import type { CF_SEO } from "src/@types/Contentful_SEO"; +import type { CF_ComponentLayout } from "src/@types/Contentful_Layout"; +import type { CF_PostEntrySkeleton } from "src/@types/Contentful_Post"; +import type { CF_TagSkeleton } from "src/@types/Contentful_Tag"; +import type { Document } from "@contentful/rich-text-types"; + +export interface CF_Post_Overview extends CF_Content, CF_SEO { + id: string; + headline: string; + text: Document; + layout: CF_ComponentLayout; + allPosts: boolean; + filterByTag: CF_TagSkeleton[]; + posts: CF_PostEntrySkeleton[]; + numberItems: number; + design?: "cards" | "list"; +} + +export type CF_Post_OverviewEntrySkeleton = { + contentTypeId: CF_ContentType.post; + fields: CF_Post_Overview; +}; diff --git a/middlelayer/__cms/Contentful_Quote.ts b/middlelayer/__cms/Contentful_Quote.ts new file mode 100644 index 0000000..1c7c9ea --- /dev/null +++ b/middlelayer/__cms/Contentful_Quote.ts @@ -0,0 +1,14 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum.js"; +import type { CF_ComponentLayout } from "./Contentful_Layout.js"; + +export interface CF_Quote { + quote: string; + author: string; + variant: "left" | "right"; + layout: CF_ComponentLayout; +} + +export type CF_QuoteSkeleton = { + contentTypeId: CF_ContentType.quote; + fields: CF_Quote; +}; diff --git a/middlelayer/__cms/Contentful_Richtext.ts b/middlelayer/__cms/Contentful_Richtext.ts new file mode 100644 index 0000000..1af8dc8 --- /dev/null +++ b/middlelayer/__cms/Contentful_Richtext.ts @@ -0,0 +1,11 @@ +import type { CF_ContentType } from "src/@types/Contentful_ContentType.enum"; + +export interface CF_ComponentRichtext { + content?: Document; + layout?: any; +} + +export interface CF_ComponentRichtextSkeleton { + contentTypeId: CF_ContentType.richtext + fields: CF_ComponentRichtext +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_SEO.ts b/middlelayer/__cms/Contentful_SEO.ts new file mode 100644 index 0000000..1abc4bf --- /dev/null +++ b/middlelayer/__cms/Contentful_SEO.ts @@ -0,0 +1,8 @@ + +export type metaRobots = "index, follow" | "noindex, follow" | "index, nofollow" | "noindex, nofollow"; + +export interface CF_SEO { + seoTitle : string, + seoMetaRobots : metaRobots, + seoDescription : string, +} \ No newline at end of file diff --git a/middlelayer/__cms/Contentful_SkeletonTypes.ts b/middlelayer/__cms/Contentful_SkeletonTypes.ts new file mode 100644 index 0000000..9c7cc14 --- /dev/null +++ b/middlelayer/__cms/Contentful_SkeletonTypes.ts @@ -0,0 +1,6 @@ +import type { CF_PostEntrySkeleton } from "src/@types/Contentful_Post"; +import type { CF_NavigationSkeleton } from "src/@types/Contentful_Navigation"; +import type { CF_PageSkeleton } from "src/@types/Contentful_Page"; +import type { CF_PageConfigSkeleton } from "src/@types/Contentful_PageConfig"; + +export type CF_SkeletonTypes = CF_PostEntrySkeleton | CF_NavigationSkeleton | CF_PageSkeleton | CF_PageConfigSkeleton; diff --git a/middlelayer/__cms/Contentful_Tag.ts b/middlelayer/__cms/Contentful_Tag.ts new file mode 100644 index 0000000..c6d0e58 --- /dev/null +++ b/middlelayer/__cms/Contentful_Tag.ts @@ -0,0 +1,11 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; + +export interface CF_Tag { + name: string; + icon: string; +} + +export interface CF_TagSkeleton { + contentTypeId: CF_ContentType.tag; + fields: CF_Tag; +} diff --git a/middlelayer/__cms/Contentful_YoutubeVideo.ts b/middlelayer/__cms/Contentful_YoutubeVideo.ts new file mode 100644 index 0000000..63a72cf --- /dev/null +++ b/middlelayer/__cms/Contentful_YoutubeVideo.ts @@ -0,0 +1,16 @@ +import type { CF_ContentType } from "./Contentful_ContentType.enum"; +import type { CF_ComponentLayout } from "./Contentful_Layout"; + +export interface CF_YoutubeVideo { + id: string; + youtubeId: string; + params?: string; + title?: string; + description?: string; + layout: CF_ComponentLayout; +} + +export interface CF_ComponentYoutubeVideoSkeleton { + contentTypeId: CF_ContentType.youtubeVideo; + fields: CF_YoutubeVideo; +} diff --git a/middlelayer/__cms/SeoProps.ts b/middlelayer/__cms/SeoProps.ts new file mode 100644 index 0000000..1ebc756 --- /dev/null +++ b/middlelayer/__cms/SeoProps.ts @@ -0,0 +1,72 @@ +export type TwitterCardType = "summary" | "summary_large_image" | "app" | "player"; + +export interface Link extends HTMLLinkElement { + prefetch: boolean; + crossorigin: string; +} + +export interface Meta extends HTMLMetaElement { + property: string; +} + +export interface SeoProperties { + title?: string; + titleTemplate?: string; + titleDefault?: string; + charset?: string; + description?: string; + canonical?: URL | string; + nofollow?: boolean; + noindex?: boolean; + languageAlternates?: { + href: URL | string; + hrefLang: string; + }[]; + openGraph?: { + basic: { + title: string; + type: string; + image: string; + url?: URL | string; + }; + optional?: { + audio?: string; + description?: string; + determiner?: string; + locale?: string; + localeAlternate?: string[]; + siteName?: string; + video?: string; + }; + image?: { + url?: URL | string; + secureUrl?: URL | string; + type?: string; + width?: number; + height?: number; + alt?: string; + }; + article?: { + publishedTime?: string; + modifiedTime?: string; + expirationTime?: string; + authors?: string[]; + section?: string; + tags?: string[]; + }; + }; + twitter?: { + card?: TwitterCardType; + site?: string; + creator?: string; + title?: string; + description?: string; + image?: URL | string; + imageAlt?: string; + }; + extend?: { + link?: Partial[]; + meta?: Partial[]; + }; + surpressWarnings?: boolean; +} diff --git a/middlelayer/__cms/TextAlignment.ts b/middlelayer/__cms/TextAlignment.ts new file mode 100644 index 0000000..142bf48 --- /dev/null +++ b/middlelayer/__cms/TextAlignment.ts @@ -0,0 +1,6 @@ +export type TextAlignment = 'left' | 'center' | 'right'; +export enum TextAlignmentClasses { + 'left' = 'text-left', + 'center' = 'text-center', + 'right' = 'text-right' +} diff --git a/middlelayer/adapters/Mock/_cms/mockNavigation.ts b/middlelayer/adapters/Mock/_cms/mockNavigation.ts new file mode 100644 index 0000000..eebe600 --- /dev/null +++ b/middlelayer/adapters/Mock/_cms/mockNavigation.ts @@ -0,0 +1,37 @@ +import type { Navigation } from "../../../types/cms/Navigation"; +import type { Link } from "../../../types/cms/Link"; +import type { Page } from "../../../types/cms/Page"; +import { generateMockPages } from "./mockPage"; + +/** + * Generiert Mock-Navigation mit locale-spezifischen Inhalten + * @param locale - Die gewünschte Locale ("de" oder "en") + */ +export function generateMockNavigation(locale: string = "de"): Navigation { + const isEn = locale === "en"; + const pages = generateMockPages(locale); + + // Erstelle Links basierend auf den Pages + const links: Array<{ fields: Link | Page }> = [ + { + fields: pages["/"] as Page, + }, + { + fields: pages["/about"] as Page, + }, + { + fields: { + name: isEn ? "Products" : "Produkte", + internal: "products", + linkName: isEn ? "Products" : "Produkte", + url: "/products", + } as Link, + }, + ]; + + return { + name: isEn ? "Main Navigation" : "Hauptnavigation", + internal: "main-nav", + links: links as any, + }; +} diff --git a/middlelayer/adapters/Mock/_cms/mockPage.ts b/middlelayer/adapters/Mock/_cms/mockPage.ts new file mode 100644 index 0000000..fa5a7b6 --- /dev/null +++ b/middlelayer/adapters/Mock/_cms/mockPage.ts @@ -0,0 +1,871 @@ +import type { Page } from "../../../types/cms/Page"; +import type { HTMLSkeleton } from "../../../types/cms/Html"; +import type { MarkdownSkeleton } from "../../../types/cms/Markdown"; +import type { ComponentIframeSkeleton } from "../../../types/cms/Iframe"; +import type { ImageGallerySkeleton } from "../../../types/cms/ImageGallery"; +import type { ComponentImageSkeleton } from "../../../types/cms/Image"; +import type { QuoteSkeleton } from "../../../types/cms/Quote"; +import type { ComponentYoutubeVideoSkeleton } from "../../../types/cms/YoutubeVideo"; +import type { ComponentHeadlineSkeleton } from "../../../types/cms/Headline"; +import type { ComponentImgSkeleton } from "../../../types/cms/Img"; +import { ContentType } from "../../../types/cms/ContentType.enum"; +import { FullwidthBannerVariant } from "../../../types/cms/FullwidthBanner"; + +/** + * Erstellt eine Mock HTML-Komponente + */ +function createMockHTML( + id: string, + html: string, + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): HTMLSkeleton { + return { + contentTypeId: ContentType.html, + fields: { + id, + html, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock Markdown-Komponente + */ +function createMockMarkdown( + name: string, + content: string, + alignment: "left" | "center" | "right" = "left", + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): MarkdownSkeleton { + return { + contentTypeId: ContentType.markdown, + fields: { + name, + content, + alignment, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock-Image-Komponente + */ +function createMockImage( + title: string, + description: string, + url: string +): ComponentImgSkeleton { + return { + contentTypeId: ContentType.img, + fields: { + title, + description, + file: { + url, + details: { + size: 100000, + image: { + width: 800, + height: 600, + }, + }, + fileName: `${title.toLowerCase().replace(/\s+/g, "-")}.jpg`, + contentType: "image/jpeg", + }, + }, + }; +} + +/** + * Erstellt eine Mock Iframe-Komponente + */ +function createMockIframe( + name: string, + content: string, + iframe: string, + overlayImageUrl?: string, + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): ComponentIframeSkeleton { + return { + contentTypeId: ContentType.iframe, + fields: { + name, + content, + iframe, + overlayImage: overlayImageUrl + ? createMockImage(name, "Overlay Image", overlayImageUrl) + : undefined, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock ImageGallery-Komponente + */ +function createMockImageGallery( + name: string, + images: Array<{ title: string; description?: string; url: string }>, + description?: string, + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): ImageGallerySkeleton { + return { + contentTypeId: ContentType.imgGallery, + fields: { + name, + images: images.map((img) => + createMockImage(img.title, img.description || "", img.url) + ), + description, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock Image-Komponente + */ +function createMockImageComponent( + name: string, + imageUrl: string, + caption: string, + maxWidth?: number, + aspectRatio?: number, + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): ComponentImageSkeleton { + return { + contentTypeId: ContentType.image, + fields: { + name, + image: createMockImage(name, caption, imageUrl), + caption, + maxWidth, + aspectRatio, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock Quote-Komponente + */ +function createMockQuote( + quote: string, + author: string, + variant: "left" | "right" = "left", + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): QuoteSkeleton { + return { + contentTypeId: ContentType.quote, + fields: { + quote, + author, + variant, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock YouTube-Video-Komponente + */ +function createMockYoutubeVideo( + id: string, + youtubeId: string, + params?: string, + title?: string, + description?: string, + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): ComponentYoutubeVideoSkeleton { + return { + contentTypeId: ContentType.youtubeVideo, + fields: { + id, + youtubeId, + params, + title, + description, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Erstellt eine Mock Headline-Komponente + */ +function createMockHeadline( + internal: string, + text: string, + tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" = "h2", + align?: "left" | "center" | "right", + mobile: string = "12", + tablet?: string, + desktop?: string, + spaceBottom?: number +): ComponentHeadlineSkeleton { + return { + contentTypeId: ContentType.headline, + fields: { + internal, + text, + tag, + align, + layout: { + mobile: mobile as any, + tablet: tablet as any, + desktop: desktop as any, + spaceBottom: spaceBottom as any, + }, + }, + }; +} + +/** + * Generiert Mock-Seiten mit locale-spezifischen Inhalten + * @param locale - Die gewünschte Locale ("de" oder "en") + * @returns Record von Seiten mit locale-spezifischen Inhalten + */ +export function generateMockPages(locale: string = "de"): Record { + const isEn = locale === "en"; + + return { + "/": { + slug: "/", + name: isEn ? "Home" : "Home", + linkName: isEn ? "Home" : "Startseite", + headline: isEn + ? "Welcome to our website" + : "Willkommen auf unserer Website", + subheadline: isEn + ? "Discover our products and services" + : "Entdecken Sie unsere Produkte und Dienstleistungen", + seoTitle: isEn ? "Home - Welcome" : "Home - Willkommen", + seoMetaRobots: "index, follow", + seoDescription: isEn + ? "Welcome to our website. Discover our products and services." + : "Willkommen auf unserer Website. Entdecken Sie unsere Produkte und Dienstleistungen.", + row1JustifyContent: "center", + row1AlignItems: "center", + row1Content: [ + createMockMarkdown( + "welcome-intro", + isEn + ? "# Welcome\n\nThis is a **markdown** component showcasing our content system.\n\n- Feature 1\n- Feature 2\n- Feature 3" + : "# Willkommen\n\nDies ist eine **Markdown**-Komponente, die unser Content-System zeigt.\n\n- Funktion 1\n- Funktion 2\n- Funktion 3", + "center", + "12", + "10", + "8", + 2 + ), + ], + row2JustifyContent: "start", + row2AlignItems: "start", + row2Content: [ + createMockHTML( + "intro-html", + isEn + ? '

HTML Content

This is an HTML component with custom styling.

' + : '

HTML Inhalt

Dies ist eine HTML-Komponente mit benutzerdefiniertem Styling.

', + "12", + "6", + "6", + 1.5 + ), + createMockMarkdown( + "features", + isEn + ? "## Features\n\n- Fast and reliable\n- Modern technology\n- Great support" + : "## Funktionen\n\n- Schnell und zuverlässig\n- Moderne Technologie\n- Großer Support", + "left", + "12", + "6", + "6", + 1.5 + ), + ], + row3JustifyContent: "start", + row3AlignItems: "start", + row3Content: [ + createMockMarkdown( + "footer-note", + isEn + ? "---\n\n*Thank you for visiting our website!*" + : "---\n\n*Vielen Dank für Ihren Besuch auf unserer Website!*", + "center", + "12", + "8", + "6" + ), + ], + topFullwidthBanner: { + contentTypeId: ContentType.fullwidthBanner, + fields: { + name: "home-banner", + variant: FullwidthBannerVariant.light, + headline: isEn ? "Welcome" : "Herzlich Willkommen", + subheadline: isEn + ? "Your solution for all needs" + : "Ihre Lösung für alle Bedürfnisse", + text: isEn + ? "Discover our diverse range of offerings and find exactly what you're looking for." + : "Entdecken Sie unsere vielfältigen Angebote und finden Sie genau das, was Sie suchen.", + image: [], + img: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? "Home Banner" : "Home Banner", + description: isEn + ? "Banner for the homepage" + : "Banner für die Startseite", + file: { + url: "https://picsum.photos/1200/400?random=home", + details: { + size: 45678, + image: { + width: 1200, + height: 400, + }, + }, + fileName: "home-banner.jpg", + contentType: "image/jpeg", + }, + }, + }, + }, + }, + }, + "/about": { + slug: "/about", + name: isEn ? "About Us" : "Über uns", + linkName: isEn ? "About Us" : "Über uns", + headline: isEn ? "About Us" : "Über uns", + subheadline: isEn + ? "Get to know us better" + : "Lernen Sie uns besser kennen", + seoTitle: isEn ? "About Us - Our Story" : "Über uns - Unsere Geschichte", + seoMetaRobots: "index, follow", + seoDescription: isEn + ? "Learn more about our company, our values and our mission." + : "Erfahren Sie mehr über unsere Firma, unsere Werte und unsere Mission.", + row1JustifyContent: "start", + row1AlignItems: "start", + row1Content: [ + createMockMarkdown( + "about-intro", + isEn + ? "# Our Story\n\nWe are a company dedicated to providing excellent service and innovative solutions." + : "# Unsere Geschichte\n\nWir sind ein Unternehmen, das sich der Bereitstellung exzellenter Dienstleistungen und innovativer Lösungen widmet.", + "left", + "12", + "8", + "8" + ), + ], + row2JustifyContent: "between", + row2AlignItems: "start", + row2Content: [ + createMockHTML( + "mission", + isEn + ? '

Our Mission

To deliver exceptional value to our customers.

' + : '

Unsere Mission

Außergewöhnlichen Mehrwert für unsere Kunden zu schaffen.

', + "12", + "5", + "5", + 1 + ), + createMockHTML( + "vision", + isEn + ? '

Our Vision

To be the leading provider in our industry.

' + : '

Unsere Vision

Der führende Anbieter in unserer Branche zu sein.

', + "12", + "5", + "5", + 1 + ), + ], + row3JustifyContent: "start", + row3AlignItems: "start", + row3Content: [ + createMockMarkdown( + "values", + isEn + ? "## Our Values\n\n1. **Integrity** - We do what we say\n2. **Innovation** - We embrace new ideas\n3. **Excellence** - We strive for the best" + : "## Unsere Werte\n\n1. **Integrität** - Wir halten, was wir versprechen\n2. **Innovation** - Wir begrüßen neue Ideen\n3. **Exzellenz** - Wir streben nach dem Besten", + "left", + "12", + "10", + "8" + ), + ], + topFullwidthBanner: { + contentTypeId: ContentType.fullwidthBanner, + fields: { + name: "about-banner", + variant: FullwidthBannerVariant.dark, + headline: isEn ? "About Us" : "Über uns", + subheadline: isEn + ? "Our Story and Values" + : "Unsere Geschichte und Werte", + text: isEn + ? "For many years, we have been your reliable partner for innovative solutions." + : "Seit vielen Jahren sind wir Ihr zuverlässiger Partner für innovative Lösungen.", + image: [], + img: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? "About Banner" : "About Banner", + description: isEn + ? "Banner for the about page" + : "Banner für die Über-uns-Seite", + file: { + url: "https://picsum.photos/1200/400?random=about", + details: { + size: 45678, + image: { + width: 1200, + height: 400, + }, + }, + fileName: "about-banner.jpg", + contentType: "image/jpeg", + }, + }, + }, + }, + }, + }, + "/404": { + slug: "/404", + name: isEn ? "404" : "404", + linkName: isEn ? "404" : "404", + headline: isEn ? "404 - Page Not Found" : "404 - Seite nicht gefunden", + subheadline: isEn + ? "The page you are looking for does not exist." + : "Die gesuchte Seite existiert nicht.", + seoTitle: isEn ? "Page Not Found" : "Seite nicht gefunden", + seoMetaRobots: "noindex, follow", + seoDescription: isEn + ? "The page you are looking for does not exist." + : "Die gesuchte Seite existiert nicht.", + row1JustifyContent: "center", + row1AlignItems: "center", + row1Content: [ + createMockMarkdown( + "404-message", + isEn + ? "# Page Not Found\n\nThe page you are looking for does not exist or has been moved.\n\nPlease check the URL or return to the [homepage](/)." + : "# Seite nicht gefunden\n\nDie gesuchte Seite existiert nicht oder wurde verschoben.\n\nBitte überprüfen Sie die URL oder kehren Sie zur [Startseite](/) zurück.", + "center", + "12", + "10", + "8" + ), + ], + row2JustifyContent: "start", + row2AlignItems: "start", + row2Content: [], + row3JustifyContent: "start", + row3AlignItems: "start", + row3Content: [], + topFullwidthBanner: { + contentTypeId: ContentType.fullwidthBanner, + fields: { + name: "404-banner", + variant: FullwidthBannerVariant.dark, + headline: isEn ? "404" : "404", + subheadline: isEn ? "Page Not Found" : "Seite nicht gefunden", + text: isEn + ? "The page you are looking for does not exist." + : "Die gesuchte Seite existiert nicht.", + image: [], + img: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? "404 Banner" : "404 Banner", + description: isEn + ? "Banner for the 404 page" + : "Banner für die 404-Seite", + file: { + url: "https://picsum.photos/1200/400?random=404", + details: { + size: 45678, + image: { + width: 1200, + height: 400, + }, + }, + fileName: "404-banner.jpg", + contentType: "image/jpeg", + }, + }, + }, + }, + }, + }, + "/500": { + slug: "/500", + name: isEn ? "500" : "500", + linkName: isEn ? "500" : "500", + headline: isEn ? "500 - Server Error" : "500 - Serverfehler", + subheadline: isEn + ? "Something went wrong on our end. Please try again later." + : "Etwas ist auf unserer Seite schiefgelaufen. Bitte versuchen Sie es später erneut.", + seoTitle: isEn ? "Server Error" : "Serverfehler", + seoMetaRobots: "noindex, follow", + seoDescription: isEn + ? "Something went wrong on our end. Please try again later." + : "Etwas ist auf unserer Seite schiefgelaufen. Bitte versuchen Sie es später erneut.", + row1JustifyContent: "center", + row1AlignItems: "center", + row1Content: [ + createMockMarkdown( + "500-message", + isEn + ? "# Server Error\n\nWe're sorry, but something went wrong on our end.\n\nOur team has been notified and is working on fixing the issue. Please try again later or return to the [homepage](/)." + : "# Serverfehler\n\nEs tut uns leid, aber etwas ist auf unserer Seite schiefgelaufen.\n\nUnser Team wurde benachrichtigt und arbeitet an der Behebung des Problems. Bitte versuchen Sie es später erneut oder kehren Sie zur [Startseite](/) zurück.", + "center", + "12", + "10", + "8" + ), + ], + row2JustifyContent: "start", + row2AlignItems: "start", + row2Content: [], + row3JustifyContent: "start", + row3AlignItems: "start", + row3Content: [], + topFullwidthBanner: { + contentTypeId: ContentType.fullwidthBanner, + fields: { + name: "500-banner", + variant: FullwidthBannerVariant.dark, + headline: isEn ? "500" : "500", + subheadline: isEn ? "Server Error" : "Serverfehler", + text: isEn + ? "Something went wrong on our end. Please try again later." + : "Etwas ist auf unserer Seite schiefgelaufen. Bitte versuchen Sie es später erneut.", + image: [], + img: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? "500 Banner" : "500 Banner", + description: isEn + ? "Banner for the 500 page" + : "Banner für die 500-Seite", + file: { + url: "https://picsum.photos/1200/400?random=500", + details: { + size: 45678, + image: { + width: 1200, + height: 400, + }, + }, + fileName: "500-banner.jpg", + contentType: "image/jpeg", + }, + }, + }, + }, + }, + }, + "/components": { + slug: "/components", + name: isEn ? "Components" : "Komponenten", + linkName: isEn ? "Components" : "Komponenten", + headline: isEn ? "Component Showcase" : "Komponenten-Showcase", + subheadline: isEn + ? "All available content components" + : "Alle verfügbaren Content-Komponenten", + seoTitle: isEn ? "Components - Showcase" : "Komponenten - Showcase", + seoMetaRobots: "index, follow", + seoDescription: isEn + ? "Showcase of all available content components." + : "Showcase aller verfügbaren Content-Komponenten.", + row1JustifyContent: "start", + row1AlignItems: "start", + row1Content: [ + createMockHeadline( + "headline-h1", + isEn ? "Component Showcase" : "Komponenten-Showcase", + "h1", + "center", + "12" + ), + createMockMarkdown( + "intro", + isEn + ? "This page demonstrates all available content components in our CMS system." + : "Diese Seite demonstriert alle verfügbaren Content-Komponenten in unserem CMS-System.", + "center", + "12", + "10", + "8" + ), + ], + row2JustifyContent: "start", + row2AlignItems: "start", + row2Content: [ + createMockHeadline( + "headline-html", + isEn ? "HTML Component" : "HTML-Komponente", + "h2", + "left", + "12", + "6", + "6" + ), + createMockHTML( + "html-example", + isEn + ? '

HTML Content

This is an HTML component with custom styling.

' + : '

HTML Inhalt

Dies ist eine HTML-Komponente mit benutzerdefiniertem Styling.

', + "12", + "6", + "6" + ), + createMockHeadline( + "headline-markdown", + isEn ? "Markdown Component" : "Markdown-Komponente", + "h2", + "left", + "12", + "6", + "6" + ), + createMockMarkdown( + "markdown-example", + isEn + ? "## Markdown Content\n\nThis is a **Markdown** component with:\n\n- Lists\n- **Bold** text\n- *Italic* text\n- [Links](/)" + : "## Markdown Inhalt\n\nDies ist eine **Markdown**-Komponente mit:\n\n- Listen\n- **Fettem** Text\n- *Kursivem* Text\n- [Links](/)", + "left", + "12", + "6", + "6" + ), + ], + row3JustifyContent: "start", + row3AlignItems: "start", + row3Content: [ + createMockHeadline( + "headline-image", + isEn ? "Image Component" : "Bild-Komponente", + "h2", + "left", + "12" + ), + createMockImageComponent( + "sample-image", + "https://picsum.photos/800/600?random=1", + isEn + ? "A sample image with caption" + : "Ein Beispielbild mit Beschriftung", + undefined, + undefined, + "12", + "8", + "6" + ), + createMockHeadline( + "headline-gallery", + isEn ? "Image Gallery" : "Bildergalerie", + "h2", + "left", + "12" + ), + createMockImageGallery( + "gallery-example", + [ + { + title: isEn ? "Image 1" : "Bild 1", + description: isEn ? "First gallery image" : "Erstes Galeriebild", + url: "https://picsum.photos/400/300?random=2", + }, + { + title: isEn ? "Image 2" : "Bild 2", + description: isEn + ? "Second gallery image" + : "Zweites Galeriebild", + url: "https://picsum.photos/400/300?random=3", + }, + { + title: isEn ? "Image 3" : "Bild 3", + description: isEn ? "Third gallery image" : "Drittes Galeriebild", + url: "https://picsum.photos/400/300?random=4", + }, + { + title: isEn ? "Image 4" : "Bild 4", + url: "https://picsum.photos/400/300?random=5", + }, + ], + isEn + ? "A collection of sample images" + : "Eine Sammlung von Beispielbildern", + "12" + ), + createMockHeadline( + "headline-quote", + isEn ? "Quote Component" : "Zitat-Komponente", + "h2", + "left", + "12" + ), + createMockQuote( + isEn + ? "The only way to do great work is to love what you do." + : "Die einzige Möglichkeit, großartige Arbeit zu leisten, ist, das zu lieben, was man tut.", + isEn ? "Steve Jobs" : "Steve Jobs", + "left", + "12", + "6", + "6" + ), + createMockQuote( + isEn + ? "Innovation distinguishes between a leader and a follower." + : "Innovation unterscheidet einen Führer von einem Anhänger.", + isEn ? "Steve Jobs" : "Steve Jobs", + "right", + "12", + "6", + "6" + ), + createMockHeadline( + "headline-youtube", + isEn ? "YouTube Video" : "YouTube-Video", + "h2", + "left", + "12" + ), + createMockYoutubeVideo( + "youtube-1", + "dQw4w9WgXcQ", + "autoplay=0", + isEn ? "Sample YouTube Video" : "Beispiel-YouTube-Video", + isEn + ? "A sample YouTube video embedded in the page" + : "Ein eingebettetes YouTube-Video auf der Seite", + "12", + "10", + "8" + ), + createMockHeadline( + "headline-iframe", + isEn ? "Iframe Component" : "Iframe-Komponente", + "h2", + "left", + "12" + ), + createMockIframe( + "iframe-example", + isEn + ? "

This is an iframe component with embedded content.

" + : "

Dies ist eine Iframe-Komponente mit eingebettetem Inhalt.

", + "https://example.com", + "https://picsum.photos/800/400?random=6", + "12", + "10", + "8" + ), + ], + topFullwidthBanner: { + contentTypeId: ContentType.fullwidthBanner, + fields: { + name: "components-banner", + variant: FullwidthBannerVariant.light, + headline: isEn ? "Component Showcase" : "Komponenten-Showcase", + subheadline: isEn + ? "All available content components" + : "Alle verfügbaren Content-Komponenten", + text: isEn + ? "Explore all the different content components available in our CMS system." + : "Entdecken Sie alle verschiedenen Content-Komponenten, die in unserem CMS-System verfügbar sind.", + image: [], + img: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? "Components Banner" : "Komponenten Banner", + description: isEn + ? "Banner for the components page" + : "Banner für die Komponenten-Seite", + file: { + url: "https://picsum.photos/1200/400?random=components", + details: { + size: 45678, + image: { + width: 1200, + height: 400, + }, + }, + fileName: "components-banner.jpg", + contentType: "image/jpeg", + }, + }, + }, + }, + }, + }, + }; +} diff --git a/middlelayer/adapters/Mock/_cms/mockPageConfig.ts b/middlelayer/adapters/Mock/_cms/mockPageConfig.ts new file mode 100644 index 0000000..b2627bc --- /dev/null +++ b/middlelayer/adapters/Mock/_cms/mockPageConfig.ts @@ -0,0 +1,50 @@ +import type { PageConfig } from "../../../types/cms/PageConfig"; +import { ContentType } from "../../../types/cms/ContentType.enum"; + +/** + * Generiert Mock-PageConfig mit locale-spezifischen Inhalten + * @param locale - Die gewünschte Locale ("de" oder "en") + */ +export function generateMockPageConfig(locale: string = "de"): PageConfig { + const isEn = locale === "en"; + + return { + logo: { + contentTypeId: ContentType.img, + fields: { + title: isEn ? 'Company Logo' : 'Firmenlogo', + description: isEn ? 'Main company logo' : 'Haupt-Firmenlogo', + file: { + url: 'https://picsum.photos/200/60?random=logo', + details: { + size: 12345, + image: { + width: 200, + height: 60, + }, + }, + fileName: 'logo.png', + contentType: 'image/png', + }, + }, + }, + footerText1: isEn + ? '© 2024 My Company. All rights reserved.' + : '© 2024 Meine Firma. Alle Rechte vorbehalten.', + seoTitle: isEn + ? 'Welcome to our website' + : 'Willkommen auf unserer Website', + seoDescription: isEn + ? 'Discover our products and services. We offer high-quality solutions for your needs.' + : 'Entdecken Sie unsere Produkte und Dienstleistungen. Wir bieten hochwertige Lösungen für Ihre Bedürfnisse.', + blogTagPageHeadline: isEn ? 'Blog Tags' : 'Blog Tags', + blogPostsPageHeadline: isEn + ? 'Our Blog Posts' + : 'Unsere Blog-Beiträge', + blogPostsPageSubHeadline: isEn + ? 'Current articles and news' + : 'Aktuelle Artikel und Neuigkeiten', + website: 'https://example.com', + }; +} + diff --git a/middlelayer/adapters/Mock/_cms/mockProducts.ts b/middlelayer/adapters/Mock/_cms/mockProducts.ts new file mode 100644 index 0000000..531a478 --- /dev/null +++ b/middlelayer/adapters/Mock/_cms/mockProducts.ts @@ -0,0 +1,113 @@ +import type { Product } from "../../../types/product"; + +const productNames = [ + "Laptop Pro 15", + "Wireless Headphones", + "Smart Watch Series 8", + "Mechanical Keyboard", + "Gaming Mouse", + "USB-C Hub", + "External SSD 1TB", + "Webcam HD 1080p", + "Standing Desk", + "Ergonomic Chair", + 'Monitor 27" 4K', + "Tablet Pro", + "Smart Speaker", + "Action Camera", + "Drone Mini", +]; + +const categories = [ + "Electronics", + "Computers", + "Audio", + "Accessories", + "Furniture", + "Photography", +]; + +const descriptions = [ + "High-performance device with cutting-edge technology", + "Premium quality product designed for professionals", + "Latest model with advanced features", + "Durable and reliable for everyday use", + "Compact design with powerful capabilities", +]; + +function getRandomElement(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; +} + +function getRandomPrice(): number { + return Math.round((Math.random() * 900 + 10) * 100) / 100; +} + +function generateProduct(id: string): Product { + const basePrice = getRandomPrice(); + + // 40% Chance auf Promotion + const hasPromotion = Math.random() < 0.4; + let price = basePrice; + let originalPrice: number | undefined; + let promotion: Product["promotion"]; + + if (hasPromotion) { + // Zufällige Promotion auswählen + const promotionType = Math.random(); + if (promotionType < 0.33) { + // Sale mit Rabatt + const discountPercent = [10, 20, 30, 40, 50][ + Math.floor(Math.random() * 5) + ]; + originalPrice = basePrice; + price = Math.round(basePrice * (1 - discountPercent / 100) * 100) / 100; + promotion = { + category: "sale", + text: `-${discountPercent}%`, + }; + } else if (promotionType < 0.66) { + // Sale (generisch) + originalPrice = basePrice; + price = Math.round(basePrice * 0.7 * 100) / 100; // 30% Rabatt + promotion = { + category: "sale", + text: "-30%", + }; + } else { + // Topseller + promotion = { + category: "topseller", + text: "top", + }; + } + } + + return { + id, + name: getRandomElement(productNames), + description: getRandomElement(descriptions), + price, + originalPrice, + currency: "EUR", + imageUrl: `https://picsum.photos/400/300?random=${id}`, + category: getRandomElement(categories), + inStock: Math.random() > 0.2, // 80% chance of being in stock + promotion, + }; +} + +export function generateRandomProducts(count: number = 4): Product[] { + const products: Product[] = []; + const usedIds = new Set(); + + while (products.length < count) { + const id = `prod-${Math.floor(Math.random() * 10000)}`; + if (!usedIds.has(id)) { + usedIds.add(id); + products.push(generateProduct(id)); + } + } + + return products; +} diff --git a/middlelayer/adapters/Mock/_i18n/mockTranslations.ts b/middlelayer/adapters/Mock/_i18n/mockTranslations.ts new file mode 100644 index 0000000..388ae4f --- /dev/null +++ b/middlelayer/adapters/Mock/_i18n/mockTranslations.ts @@ -0,0 +1,191 @@ +/** + * Mock-Daten für Übersetzungen + * Diese können später durch einen echten CMS-Adapter ersetzt werden + */ + +export interface Translation { + key: string; + value: string; + namespace?: string; +} + +export interface TranslationsData { + locale: string; + translations: Translation[]; +} + +/** + * Mock-Übersetzungen für Deutsch (de) + */ +export const mockTranslationsDe: TranslationsData = { + locale: "de", + translations: [ + // Login Modal + { key: "login.title", value: "Anmelden", namespace: "auth" }, + { key: "login.email", value: "E-Mail", namespace: "auth" }, + { key: "login.password", value: "Passwort", namespace: "auth" }, + { key: "login.submit", value: "Anmelden", namespace: "auth" }, + { key: "login.loading", value: "Wird geladen...", namespace: "auth" }, + { + key: "login.error", + value: "Login fehlgeschlagen. Bitte versuchen Sie es erneut.", + namespace: "auth", + }, + { + key: "login.noAccount", + value: "Noch kein Konto?", + namespace: "auth", + }, + { + key: "login.registerNow", + value: "Jetzt registrieren", + namespace: "auth", + }, + // Register Modal + { key: "register.title", value: "Registrieren", namespace: "auth" }, + { key: "register.name", value: "Name", namespace: "auth" }, + { key: "register.email", value: "E-Mail", namespace: "auth" }, + { key: "register.password", value: "Passwort", namespace: "auth" }, + { + key: "register.confirmPassword", + value: "Passwort bestätigen", + namespace: "auth", + }, + { key: "register.submit", value: "Registrieren", namespace: "auth" }, + { + key: "register.loading", + value: "Wird geladen...", + namespace: "auth", + }, + { + key: "register.error", + value: "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.", + namespace: "auth", + }, + { + key: "register.passwordMismatch", + value: "Passwörter stimmen nicht überein", + namespace: "auth", + }, + { + key: "register.passwordTooShort", + value: "Passwort muss mindestens 6 Zeichen lang sein", + namespace: "auth", + }, + { + key: "register.hasAccount", + value: "Bereits ein Konto?", + namespace: "auth", + }, + { + key: "register.loginNow", + value: "Jetzt anmelden", + namespace: "auth", + }, + // Navigation + { key: "nav.login", value: "Anmelden", namespace: "common" }, + { key: "nav.register", value: "Registrieren", namespace: "common" }, + { key: "nav.logout", value: "Abmelden", namespace: "common" }, + { key: "nav.greeting", value: "Hallo, {name}", namespace: "common" }, + ], +}; + +/** + * Mock-Übersetzungen für Englisch (en) + */ +export const mockTranslationsEn: TranslationsData = { + locale: "en", + translations: [ + // Login Modal + { key: "login.title", value: "Sign In", namespace: "auth" }, + { key: "login.email", value: "Email", namespace: "auth" }, + { key: "login.password", value: "Password", namespace: "auth" }, + { key: "login.submit", value: "Sign In", namespace: "auth" }, + { key: "login.loading", value: "Loading...", namespace: "auth" }, + { + key: "login.error", + value: "Login failed. Please try again.", + namespace: "auth", + }, + { + key: "login.noAccount", + value: "Don't have an account?", + namespace: "auth", + }, + { + key: "login.registerNow", + value: "Register now", + namespace: "auth", + }, + // Register Modal + { key: "register.title", value: "Register", namespace: "auth" }, + { key: "register.name", value: "Name", namespace: "auth" }, + { key: "register.email", value: "Email", namespace: "auth" }, + { key: "register.password", value: "Password", namespace: "auth" }, + { + key: "register.confirmPassword", + value: "Confirm Password", + namespace: "auth", + }, + { key: "register.submit", value: "Register", namespace: "auth" }, + { + key: "register.loading", + value: "Loading...", + namespace: "auth", + }, + { + key: "register.error", + value: "Registration failed. Please try again.", + namespace: "auth", + }, + { + key: "register.passwordMismatch", + value: "Passwords do not match", + namespace: "auth", + }, + { + key: "register.passwordTooShort", + value: "Password must be at least 6 characters long", + namespace: "auth", + }, + { + key: "register.hasAccount", + value: "Already have an account?", + namespace: "auth", + }, + { + key: "register.loginNow", + value: "Sign in now", + namespace: "auth", + }, + // Navigation + { key: "nav.login", value: "Sign In", namespace: "common" }, + { key: "nav.register", value: "Register", namespace: "common" }, + { key: "nav.logout", value: "Logout", namespace: "common" }, + { key: "nav.greeting", value: "Hello, {name}", namespace: "common" }, + ], +}; + +/** + * Gibt Übersetzungen für eine bestimmte Locale zurück + */ +export function getTranslations( + locale: string = "de", + namespace?: string +): TranslationsData { + const translations = + locale === "en" ? mockTranslationsEn : mockTranslationsDe; + + // Filter nach Namespace, falls angegeben + if (namespace) { + return { + locale: translations.locale, + translations: translations.translations.filter( + (t) => t.namespace === namespace + ), + }; + } + + return translations; +} + diff --git a/middlelayer/adapters/Mock/mockdata.ts b/middlelayer/adapters/Mock/mockdata.ts new file mode 100644 index 0000000..af37c98 --- /dev/null +++ b/middlelayer/adapters/Mock/mockdata.ts @@ -0,0 +1,93 @@ +import type { DataAdapter } from "../interface"; +import type { PageSeo, Page, Navigation, Product } from "../../types/index"; +import { generateMockPageConfig } from "./_cms/mockPageConfig"; +import { generateMockPages } from "./_cms/mockPage"; +import { generateMockNavigation } from "./_cms/mockNavigation"; +import { generateRandomProducts } from "./_cms/mockProducts"; +import { PageMapper } from "../../mappers/pageMapper"; +import { getTranslations } from "./_i18n/mockTranslations"; +import type { TranslationsData } from "./_i18n/mockTranslations"; + +/** + * Mockdata Adapter - verwendet lokale Mock-Daten + */ +export class MockdataAdapter implements DataAdapter { + async getProducts(limit: number = 4): Promise { + return generateRandomProducts(limit); + } + + async getProduct(id: string): Promise { + const products = generateRandomProducts(1); + return products[0] ? { ...products[0], id } : null; + } + async getPage(slug: string, locale?: string): Promise { + // Verwende Locale für locale-spezifische Inhalte + const pages = generateMockPages(locale || "de"); + const page = pages[slug]; + if (!page) return null; + + return PageMapper.fromCms(page); + } + + async getPages(locale?: string): Promise { + // Verwende Locale für locale-spezifische Inhalte + const pages = generateMockPages(locale || "de"); + return PageMapper.fromCmsArray(Object.values(pages)); + } + + async getPageSeo(locale?: string): Promise { + // Verwende Locale für locale-spezifische SEO-Daten + const pageConfig = generateMockPageConfig(locale || "de"); + return { + title: pageConfig.seoTitle, + description: pageConfig.seoDescription, + metaRobotsIndex: "index", + metaRobotsFollow: "follow", + }; + } + + async getNavigation(locale?: string): Promise { + // Verwende Locale für locale-spezifische Navigation + const nav = generateMockNavigation(locale || "de"); + const pages = generateMockPages(locale || "de"); + + // Konvertiere die Links zu NavigationLink-Format + const links = nav.links.map((link: any) => { + // Wenn es eine Page ist (hat slug) + if (link.fields.slug) { + const page = pages[link.fields.slug]; + if (page) { + return { + slug: page.slug, + name: page.name, + linkName: page.linkName, + url: page.slug, + icon: page.icon, + newTab: false, + }; + } + } + // Wenn es ein Link ist + return { + name: link.fields.name || link.fields.linkName, + linkName: link.fields.linkName, + url: link.fields.url, + icon: link.fields.icon, + newTab: link.fields.newTab || false, + }; + }); + + return { + name: nav.name, + internal: nav.internal, + links: links.filter(Boolean), + }; + } + + async getTranslations( + locale: string = "de", + namespace?: string + ): Promise { + return getTranslations(locale, namespace); + } +} diff --git a/middlelayer/adapters/config.ts b/middlelayer/adapters/config.ts new file mode 100644 index 0000000..8952110 --- /dev/null +++ b/middlelayer/adapters/config.ts @@ -0,0 +1,23 @@ +import { MockdataAdapter } from "./Mock/mockdata"; +import type { DataAdapter } from "./interface"; + +/** + * Adapter-Konfiguration + * Bestimmt welcher Adapter basierend auf Environment-Variablen verwendet wird + */ +export function createAdapter(): DataAdapter { + const adapterType = process.env.DATA_ADAPTER || "mock"; + + switch (adapterType) { + case "mock": + return new MockdataAdapter(); + // Weitere Adapter können hier hinzugefügt werden: + // case 'contentful': + // return new ContentfulAdapter(process.env.CONTENTFUL_SPACE_ID!, process.env.CONTENTFUL_ACCESS_TOKEN!); + default: + console.warn( + `Unbekannter Adapter-Typ: ${adapterType}. Verwende Mock-Adapter.` + ); + return new MockdataAdapter(); + } +} diff --git a/middlelayer/adapters/interface.ts b/middlelayer/adapters/interface.ts new file mode 100644 index 0000000..2ca2ef1 --- /dev/null +++ b/middlelayer/adapters/interface.ts @@ -0,0 +1,27 @@ +import type { PageSeo, Page, Navigation, Product } from "../types/index"; +import type { + TranslationsData, +} from "./Mock/_i18n/mockTranslations"; + +/** + * Adapter Interface für Datenquellen + * Jeder Adapter muss diese Schnittstelle implementieren + */ +export interface DataAdapter { + // Product Operations + getProducts(limit?: number): Promise; + getProduct(id: string): Promise; + + // Page Operations + getPage(slug: string, locale?: string): Promise; + getPages(locale?: string): Promise; + + // SEO Operations + getPageSeo(locale?: string): Promise; + + // Navigation Operations + getNavigation(locale?: string): Promise; + + // Translation Operations + getTranslations(locale?: string, namespace?: string): Promise; +} diff --git a/middlelayer/auth/README.md b/middlelayer/auth/README.md new file mode 100644 index 0000000..682e6d5 --- /dev/null +++ b/middlelayer/auth/README.md @@ -0,0 +1,150 @@ +# Authentication & Authorization + +## Übersicht + +Der Middlelayer unterstützt JWT-basierte Authentication und Role-Based Access Control (RBAC). + +## Features + +- ✅ JWT-basierte Authentication +- ✅ Passwort-Hashing mit bcrypt +- ✅ Role-Based Access Control (Admin, Customer, Guest) +- ✅ Protected Resolvers +- ✅ User-Context in GraphQL Requests + +## User-Rollen + +- **ADMIN**: Vollzugriff auf alle Ressourcen +- **CUSTOMER**: Zugriff auf Kunden-spezifische Ressourcen +- **GUEST**: Nur öffentliche Ressourcen + +## GraphQL Mutations + +### Register + +```graphql +mutation Register { + register(email: "user@example.com", password: "secure123", name: "Max Mustermann") { + user { + id + email + name + role + } + token + } +} +``` + +### Login + +```graphql +mutation Login { + login(email: "user@example.com", password: "secure123") { + user { + id + email + name + role + } + token + } +} +``` + +## GraphQL Queries + +### Aktueller User + +```graphql +query Me { + me { + id + email + name + role + } +} +``` + +## Authorization in Resolvers + +### Beispiel: Protected Resolver + +```typescript +import { requireAuth, requireAdmin } from "./auth/authorization.js"; + +export const resolvers = { + Query: { + adminOnlyData: async (_: unknown, __: unknown, context: GraphQLContext) => { + // Prüft ob User Admin ist + requireAdmin(context.user); + + // Resolver-Logik... + }, + }, +}; +``` + +### Verfügbare Authorization-Helper + +- `requireAuth(user)` - Prüft ob User authentifiziert ist +- `requireRole(user, roles)` - Prüft ob User eine bestimmte Rolle hat +- `requireAdmin(user)` - Prüft ob User Admin ist +- `requireCustomer(user)` - Prüft ob User Customer oder Admin ist + +## Verwendung im Frontend + +### Token speichern + +```typescript +// Nach Login/Register +const { token } = await login(email, password); +localStorage.setItem('authToken', token); +``` + +### Token in Requests verwenden + +```typescript +const response = await fetch('http://localhost:4000', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, + }, + body: JSON.stringify({ query, variables }), +}); +``` + +## Konfiguration + +### Environment Variables + +```bash +JWT_SECRET=your-secret-key-change-in-production +JWT_EXPIRES_IN=7d # Token-Gültigkeitsdauer +``` + +**Wichtig:** In Production muss `JWT_SECRET` sicher gesetzt werden! + +## Security Best Practices + +1. **JWT Secret**: Verwende einen starken, zufälligen Secret +2. **HTTPS**: Immer HTTPS in Production verwenden +3. **Token Expiration**: Setze angemessene Expiration-Zeiten +4. **Password Hashing**: Passwörter werden automatisch mit bcrypt gehasht +5. **Rate Limiting**: (Noch zu implementieren) Verhindere Brute-Force-Angriffe + +## Mock User Store + +Aktuell werden User in einem In-Memory Store gespeichert. Für Production sollte dies durch eine Datenbank ersetzt werden. + +## Nächste Schritte + +- [ ] Database-Integration für User-Speicherung +- [ ] Refresh Tokens +- [ ] Password Reset +- [ ] Email Verification +- [ ] Rate Limiting für Login/Register +- [ ] Session Management + diff --git a/middlelayer/auth/authorization.ts b/middlelayer/auth/authorization.ts new file mode 100644 index 0000000..04ed48a --- /dev/null +++ b/middlelayer/auth/authorization.ts @@ -0,0 +1,59 @@ +import { GraphQLError } from "graphql"; +import type { User, UserRole } from "../types/user.js"; +import { UserRole as UserRoleEnum } from "../types/user.js"; + +/** + * Authorization-Fehler + */ +export class AuthorizationError extends GraphQLError { + constructor(message: string) { + super(message, { + extensions: { + code: "UNAUTHORIZED", + }, + }); + } +} + +/** + * Prüft ob User authentifiziert ist + */ +export function requireAuth(user: User | null): User { + if (!user) { + throw new AuthorizationError("Authentifizierung erforderlich"); + } + return user; +} + +/** + * Prüft ob User eine bestimmte Rolle hat + */ +export function requireRole( + user: User | null, + requiredRoles: UserRole[] +): User { + const authenticatedUser = requireAuth(user); + + if (!requiredRoles.includes(authenticatedUser.role)) { + throw new AuthorizationError( + `Zugriff verweigert. Erforderliche Rollen: ${requiredRoles.join(", ")}` + ); + } + + return authenticatedUser; +} + +/** + * Prüft ob User Admin ist + */ +export function requireAdmin(user: User | null): User { + return requireRole(user, [UserRoleEnum.ADMIN]); +} + +/** + * Prüft ob User Customer oder Admin ist + */ +export function requireCustomer(user: User | null): User { + return requireRole(user, [UserRoleEnum.CUSTOMER, UserRoleEnum.ADMIN]); +} + diff --git a/middlelayer/auth/jwt.ts b/middlelayer/auth/jwt.ts new file mode 100644 index 0000000..a98fe8c --- /dev/null +++ b/middlelayer/auth/jwt.ts @@ -0,0 +1,63 @@ +import jwt from "jsonwebtoken"; +import type { JWTPayload, UserRole } from "../types/user.js"; +import { logger } from "../monitoring/logger.js"; + +const JWT_SECRET = + process.env.JWT_SECRET || "your-secret-key-change-in-production"; +const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "7d"; + +/** + * Erstellt ein JWT Token für einen User + */ +export function createToken(payload: JWTPayload): string { + return jwt.sign(payload, JWT_SECRET, { + expiresIn: JWT_EXPIRES_IN, + }); +} + +/** + * Verifiziert ein JWT Token + */ +export function verifyToken(token: string): JWTPayload | null { + try { + const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload; + return decoded; + } catch (error) { + logger.warn("JWT verification failed", { error }); + return null; + } +} + +/** + * Extrahiert Token aus Authorization Header + */ +export function extractTokenFromHeader( + authHeader: string | null +): string | null { + if (!authHeader) return null; + + // Format: "Bearer " + const parts = authHeader.split(" "); + if (parts.length !== 2 || parts[0] !== "Bearer") { + return null; + } + + return parts[1]; +} + +/** + * Prüft ob User eine bestimmte Rolle hat + */ +export function hasRole( + userRole: UserRole, + requiredRoles: UserRole[] +): boolean { + return requiredRoles.includes(userRole); +} + +/** + * Prüft ob User Admin ist + */ +export function isAdmin(userRole: UserRole): boolean { + return userRole === UserRole.ADMIN; +} diff --git a/middlelayer/auth/password.ts b/middlelayer/auth/password.ts new file mode 100644 index 0000000..fb2f7dc --- /dev/null +++ b/middlelayer/auth/password.ts @@ -0,0 +1,31 @@ +import bcrypt from "bcryptjs"; +import { logger } from "../monitoring/logger.js"; + +const SALT_ROUNDS = 10; + +/** + * Hasht ein Passwort + */ +export async function hashPassword(password: string): Promise { + try { + return await bcrypt.hash(password, SALT_ROUNDS); + } catch (error) { + logger.error("Password hashing failed", { error }); + throw new Error("Fehler beim Hashen des Passworts"); + } +} + +/** + * Vergleicht ein Passwort mit einem Hash + */ +export async function comparePassword( + password: string, + hash: string +): Promise { + try { + return await bcrypt.compare(password, hash); + } catch (error) { + logger.error("Password comparison failed", { error }); + return false; + } +} diff --git a/middlelayer/auth/userService.ts b/middlelayer/auth/userService.ts new file mode 100644 index 0000000..127daaa --- /dev/null +++ b/middlelayer/auth/userService.ts @@ -0,0 +1,140 @@ +import type { + User, + UserRole, + LoginCredentials, + RegisterData, +} from "../types/user.js"; +import { hashPassword, comparePassword } from "./password.js"; +import { createToken, verifyToken } from "./jwt.js"; +import { logger } from "../monitoring/logger.js"; + +/** + * Mock User Store (später durch Datenbank ersetzen) + */ +const users = new Map(); + +/** + * User Service für Authentication + */ +export class UserService { + /** + * Registriert einen neuen User + */ + async register( + data: RegisterData, + role: UserRole = "customer" + ): Promise<{ + user: User; + token: string; + }> { + // Prüfe ob User bereits existiert + const existingUser = Array.from(users.values()).find( + (u) => u.email === data.email + ); + if (existingUser) { + throw new Error("User mit dieser E-Mail existiert bereits"); + } + + // Hashe Passwort + const passwordHash = await hashPassword(data.password); + + // Erstelle User + const user: User = { + id: `user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + email: data.email, + name: data.name, + role, + createdAt: new Date(), + }; + + // Speichere User + users.set(user.id, { ...user, passwordHash }); + + // Erstelle Token + const token = createToken({ + userId: user.id, + email: user.email, + role: user.role, + }); + + logger.info("User registered", { userId: user.id, email: user.email }); + + return { user, token }; + } + + /** + * Login eines Users + */ + async login(credentials: LoginCredentials): Promise<{ + user: User; + token: string; + }> { + // Finde User + const userEntry = Array.from(users.values()).find( + (u) => u.email === credentials.email + ); + + if (!userEntry) { + throw new Error("Ungültige E-Mail oder Passwort"); + } + + // Vergleiche Passwort + const isValid = await comparePassword( + credentials.password, + userEntry.passwordHash + ); + + if (!isValid) { + throw new Error("Ungültige E-Mail oder Passwort"); + } + + // Erstelle User-Objekt ohne Passwort + const user: User = { + id: userEntry.id, + email: userEntry.email, + name: userEntry.name, + role: userEntry.role, + createdAt: userEntry.createdAt, + }; + + // Erstelle Token + const token = createToken({ + userId: user.id, + email: user.email, + role: user.role, + }); + + logger.info("User logged in", { userId: user.id, email: user.email }); + + return { user, token }; + } + + /** + * Holt User anhand der ID + */ + async getUserById(userId: string): Promise { + const userEntry = users.get(userId); + if (!userEntry) return null; + + return { + id: userEntry.id, + email: userEntry.email, + name: userEntry.name, + role: userEntry.role, + createdAt: userEntry.createdAt, + }; + } + + /** + * Holt User anhand des Tokens + */ + async getUserFromToken(token: string): Promise { + const payload = verifyToken(token); + if (!payload) return null; + + return this.getUserById(payload.userId); + } +} + +// Singleton-Instanz +export const userService = new UserService(); diff --git a/middlelayer/config/cache.ts b/middlelayer/config/cache.ts new file mode 100644 index 0000000..b709d0e --- /dev/null +++ b/middlelayer/config/cache.ts @@ -0,0 +1,19 @@ +/** + * Cache-Konfiguration + * TTL-Werte in Millisekunden + */ +export const cacheConfig = { + pages: { + ttl: parseInt(process.env.CACHE_PAGES_TTL || '60000'), // 60 Sekunden + }, + pageSeo: { + ttl: parseInt(process.env.CACHE_PAGE_SEO_TTL || '300000'), // 5 Minuten + }, + navigation: { + ttl: parseInt(process.env.CACHE_NAVIGATION_TTL || '300000'), // 5 Minuten + }, + products: { + ttl: parseInt(process.env.CACHE_PRODUCTS_TTL || '30000'), // 30 Sekunden + }, +} as const; + diff --git a/middlelayer/dataService.ts b/middlelayer/dataService.ts new file mode 100644 index 0000000..b12a0f1 --- /dev/null +++ b/middlelayer/dataService.ts @@ -0,0 +1,129 @@ +import type { DataAdapter } from "./adapters/interface.js"; +import { createAdapter } from "./adapters/config.js"; +import type { PageSeo, Page, Navigation, Product } from "./types/index.js"; +import type { TranslationsData } from "./adapters/Mock/_i18n/mockTranslations.js"; +import { AdapterError } from "./utils/errors.js"; +import { cache } from "./utils/cache.js"; +import { CacheKeyBuilder } from "./utils/cacheKeys.js"; +import { DataServiceHelpers } from "./utils/dataServiceHelpers.js"; + +/** + * DataService - Aggregator für Datenoperationen + * Verwendet den konfigurierten Adapter für alle Datenzugriffe + * Mit Caching und Fehlerbehandlung + */ +class DataService { + private adapter: DataAdapter; + + constructor(adapter?: DataAdapter) { + this.adapter = adapter || createAdapter(); + } + + /** + * Setzt einen neuen Adapter + */ + async setAdapter(adapter: DataAdapter): Promise { + this.adapter = adapter; + // Cache leeren bei Adapter-Wechsel + await Promise.all([ + cache.pages.clear(), + cache.pageSeo.clear(), + cache.navigation.clear(), + cache.products.clear(), + ]); + } + + /** + * Holt eine einzelne Seite + */ + async getPage(slug: string, locale?: string): Promise { + return DataServiceHelpers.withCacheAndMetrics( + "getPage", + cache.pages, + CacheKeyBuilder.page(slug, locale), + () => this.adapter.getPage(slug, locale), + `Fehler beim Laden der Seite '${slug}'`, + { slug, locale } + ); + } + + /** + * Holt alle Seiten + */ + async getPages(locale?: string): Promise { + return DataServiceHelpers.withCache( + cache.pages, + CacheKeyBuilder.pages(locale), + () => this.adapter.getPages(locale), + "Fehler beim Laden der Seiten" + ); + } + + /** + * Holt SEO-Daten + */ + async getPageSeo(locale?: string): Promise { + return DataServiceHelpers.withCache( + cache.pageSeo, + CacheKeyBuilder.pageSeo(locale), + () => this.adapter.getPageSeo(locale), + "Fehler beim Laden der SEO-Daten" + ); + } + + /** + * Holt Navigation + */ + async getNavigation(locale?: string): Promise { + return DataServiceHelpers.withCache( + cache.navigation, + CacheKeyBuilder.navigation(locale), + () => this.adapter.getNavigation(locale), + "Fehler beim Laden der Navigation" + ); + } + + /** + * Holt Produkte + */ + async getProducts(limit?: number): Promise { + return DataServiceHelpers.withCacheAndMetrics( + "getProducts", + cache.products, + CacheKeyBuilder.products(limit), + () => this.adapter.getProducts(limit), + "Fehler beim Laden der Produkte", + { limit } + ); + } + + /** + * Holt ein einzelnes Produkt + */ + async getProduct(id: string): Promise { + return DataServiceHelpers.withCache( + cache.products, + CacheKeyBuilder.product(id), + () => this.adapter.getProduct(id), + `Fehler beim Laden des Produkts '${id}'` + ); + } + + /** + * Holt Übersetzungen + */ + async getTranslations( + locale: string = "de", + namespace?: string + ): Promise { + return DataServiceHelpers.withCache( + cache.pages, + CacheKeyBuilder.translations(locale, namespace), + () => this.adapter.getTranslations(locale, namespace), + `Fehler beim Laden der Übersetzungen für '${locale}'` + ); + } +} + +// Singleton-Instanz mit konfiguriertem Adapter +export const dataService = new DataService(); diff --git a/middlelayer/index.ts b/middlelayer/index.ts new file mode 100644 index 0000000..4608129 --- /dev/null +++ b/middlelayer/index.ts @@ -0,0 +1,119 @@ +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; +import { createServer } from "http"; +import { typeDefs } from "./schema.js"; +import { resolvers } from "./resolvers.js"; +import { queryComplexityPlugin } from "./plugins/queryComplexity.js"; +import { createResponseCachePlugin } from "./plugins/responseCache.js"; +import { + monitoringPlugin, + queryComplexityMonitoringPlugin, +} from "./plugins/monitoring.js"; +import { createContext, type GraphQLContext } from "./utils/dataloaders.js"; +import { logger } from "./monitoring/logger.js"; +import { getMetrics } from "./monitoring/metrics.js"; +import { extractTokenFromHeader } from "./auth/jwt.js"; +import { userService } from "./auth/userService.js"; + +const PORT = process.env.PORT ? parseInt(process.env.PORT) : 4000; +const METRICS_PORT = process.env.METRICS_PORT + ? parseInt(process.env.METRICS_PORT) + : 9090; + +// Konfiguration aus Environment Variables +const MAX_QUERY_COMPLEXITY = process.env.MAX_QUERY_COMPLEXITY + ? parseInt(process.env.MAX_QUERY_COMPLEXITY) + : 1000; + +/** + * Startet einen separaten HTTP-Server für Metrics (Prometheus) + */ +function startMetricsServer() { + const server = createServer(async (req, res) => { + if (req.url === "/metrics" && req.method === "GET") { + try { + const metrics = await getMetrics(); + res.writeHead(200, { "Content-Type": "text/plain; version=0.0.4" }); + res.end(metrics); + } catch (error) { + logger.error("Failed to get metrics", { error }); + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Failed to get metrics" })); + } + } else if (req.url === "/health" && req.method === "GET") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok", service: "graphql-middlelayer" })); + } else { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not found" })); + } + }); + + server.listen(METRICS_PORT, () => { + logger.info( + `📊 Metrics Server läuft auf: http://localhost:${METRICS_PORT}/metrics` + ); + logger.info( + `❤️ Health Check verfügbar unter: http://localhost:${METRICS_PORT}/health` + ); + }); + + return server; +} + +async function startServer() { + const server = new ApolloServer({ + typeDefs, + resolvers, + plugins: [ + // Monitoring (muss zuerst sein für vollständiges Tracking) + monitoringPlugin(), + queryComplexityMonitoringPlugin(), + // Query Complexity Limit + queryComplexityPlugin({ + maxComplexity: MAX_QUERY_COMPLEXITY, + defaultComplexity: 1, + }), + // Response Caching + createResponseCachePlugin(), + ], + }); + + const { url } = await startStandaloneServer(server, { + listen: { port: PORT }, + context: async ({ req }) => { + // Extrahiere User aus Authorization Header + let user = null; + const authHeader = req.headers.authorization; + const token = extractTokenFromHeader(authHeader || null); + + if (token) { + try { + user = await userService.getUserFromToken(token); + } catch (error) { + logger.warn("Token verification failed", { error }); + } + } + + // Erstelle Context mit Dataloadern und User + return createContext(user); + }, + }); + + // Starte Metrics Server + startMetricsServer(); + + logger.info(`🚀 GraphQL Middlelayer läuft auf: ${url}`); + logger.info(`📊 GraphQL Playground verfügbar unter: ${url}`); + logger.info(`⚡ Query Complexity Limit: ${MAX_QUERY_COMPLEXITY}`); + logger.info(`💾 Response Caching: Aktiviert`); + logger.info(`🔄 Dataloader: Aktiviert`); + logger.info( + `📈 Monitoring: Aktiviert (Structured Logging, Prometheus, Tracing)` + ); +} + +startServer().catch((error) => { + logger.error("Fehler beim Starten des Servers", { error }); + process.exit(1); +}); diff --git a/middlelayer/mappers/pageMapper.ts b/middlelayer/mappers/pageMapper.ts new file mode 100644 index 0000000..fa4d3e1 --- /dev/null +++ b/middlelayer/mappers/pageMapper.ts @@ -0,0 +1,235 @@ +import type { Page as CmsPage } from "../types/cms/Page"; +import type { HTMLSkeleton } from "../types/cms/Html"; +import type { MarkdownSkeleton } from "../types/cms/Markdown"; +import type { ComponentIframeSkeleton } from "../types/cms/Iframe"; +import type { ImageGallerySkeleton } from "../types/cms/ImageGallery"; +import type { ComponentImageSkeleton } from "../types/cms/Image"; +import type { QuoteSkeleton } from "../types/cms/Quote"; +import type { ComponentYoutubeVideoSkeleton } from "../types/cms/YoutubeVideo"; +import type { ComponentHeadlineSkeleton } from "../types/cms/Headline"; +import type { Page, ContentItem, ContentRow } from "../types/page"; +import { ContentType } from "../types/cms/ContentType.enum"; + +type ContentEntry = + | HTMLSkeleton + | MarkdownSkeleton + | ComponentIframeSkeleton + | ImageGallerySkeleton + | ComponentImageSkeleton + | QuoteSkeleton + | ComponentYoutubeVideoSkeleton + | ComponentHeadlineSkeleton; + +/** + * Mapper für Page-Transformationen + * Konvertiert CMS-Typen zu unseren Domain-Typen + */ +export class PageMapper { + /** + * Strategy Pattern: Map mit Mapper-Funktionen für jeden Content-Type + */ + private static contentMappers = new Map< + ContentType, + (entry: ContentEntry) => ContentItem | null + >([ + [ + ContentType.html, + (entry) => { + const htmlEntry = entry as HTMLSkeleton; + return { + type: "html", + name: htmlEntry.fields.id || "", + html: htmlEntry.fields.html, + layout: htmlEntry.fields.layout, + }; + }, + ], + [ + ContentType.markdown, + (entry) => { + const markdownEntry = entry as MarkdownSkeleton; + return { + type: "markdown", + name: markdownEntry.fields.name, + content: markdownEntry.fields.content, + layout: markdownEntry.fields.layout, + alignment: markdownEntry.fields.alignment, + }; + }, + ], + [ + ContentType.iframe, + (entry) => { + const iframeEntry = entry as ComponentIframeSkeleton; + return { + type: "iframe", + name: iframeEntry.fields.name, + content: iframeEntry.fields.content, + iframe: iframeEntry.fields.iframe, + overlayImageUrl: iframeEntry.fields.overlayImage?.fields.file.url, + layout: iframeEntry.fields.layout, + }; + }, + ], + [ + ContentType.imgGallery, + (entry) => { + const galleryEntry = entry as ImageGallerySkeleton; + return { + type: "imageGallery", + name: galleryEntry.fields.name, + images: galleryEntry.fields.images.map((img) => ({ + url: img.fields.file.url, + title: img.fields.title, + description: img.fields.description, + })), + description: galleryEntry.fields.description, + layout: galleryEntry.fields.layout, + }; + }, + ], + [ + ContentType.image, + (entry) => { + const imageEntry = entry as ComponentImageSkeleton; + return { + type: "image", + name: imageEntry.fields.name, + imageUrl: imageEntry.fields.image.fields.file.url, + caption: imageEntry.fields.caption, + maxWidth: imageEntry.fields.maxWidth, + aspectRatio: imageEntry.fields.aspectRatio, + layout: imageEntry.fields.layout, + }; + }, + ], + [ + ContentType.quote, + (entry) => { + const quoteEntry = entry as QuoteSkeleton; + return { + type: "quote", + quote: quoteEntry.fields.quote, + author: quoteEntry.fields.author, + variant: quoteEntry.fields.variant, + layout: quoteEntry.fields.layout, + }; + }, + ], + [ + ContentType.youtubeVideo, + (entry) => { + const videoEntry = entry as ComponentYoutubeVideoSkeleton; + return { + type: "youtubeVideo", + id: videoEntry.fields.id, + youtubeId: videoEntry.fields.youtubeId, + params: videoEntry.fields.params, + title: videoEntry.fields.title, + description: videoEntry.fields.description, + layout: videoEntry.fields.layout, + }; + }, + ], + [ + ContentType.headline, + (entry) => { + const headlineEntry = entry as ComponentHeadlineSkeleton; + return { + type: "headline", + internal: headlineEntry.fields.internal, + text: headlineEntry.fields.text, + tag: headlineEntry.fields.tag, + align: headlineEntry.fields.align, + layout: headlineEntry.fields.layout, + }; + }, + ], + ]); + + /** + * Mappt ein Contentful Content-Item zu unserem ContentItem + * Verwendet Strategy Pattern für wartbaren Code + */ + private static mapContentItem(entry: ContentEntry): ContentItem | null { + if (!entry.contentTypeId || !entry.fields) { + return null; + } + + const mapper = this.contentMappers.get(entry.contentTypeId); + if (!mapper) { + return null; + } + + return mapper(entry); + } + + /** + * Mappt eine CMS Content-Row zu unserer ContentRow + */ + private static mapContentRow( + content: ContentEntry[], + justifyContent: string, + alignItems: string + ): ContentRow | undefined { + if (!content || content.length === 0) { + return undefined; + } + + const mappedContent = content + .map((entry) => this.mapContentItem(entry)) + .filter((item): item is ContentItem => item !== null); + + if (mappedContent.length === 0) { + return undefined; + } + + return { + justifyContent: justifyContent as ContentRow["justifyContent"], + alignItems: alignItems as ContentRow["alignItems"], + content: mappedContent, + }; + } + + static fromCms(cmsPage: CmsPage): Page { + return { + slug: cmsPage.slug, + name: cmsPage.name, + linkName: cmsPage.linkName, + headline: cmsPage.headline, + subheadline: cmsPage.subheadline, + seoTitle: cmsPage.seoTitle, + seoMetaRobots: cmsPage.seoMetaRobots, + seoDescription: cmsPage.seoDescription, + topFullwidthBanner: cmsPage.topFullwidthBanner + ? { + name: cmsPage.topFullwidthBanner.fields.name, + variant: cmsPage.topFullwidthBanner.fields.variant, + headline: cmsPage.topFullwidthBanner.fields.headline, + subheadline: cmsPage.topFullwidthBanner.fields.subheadline, + text: cmsPage.topFullwidthBanner.fields.text, + imageUrl: cmsPage.topFullwidthBanner.fields.img.fields.file.url, + } + : undefined, + row1: this.mapContentRow( + cmsPage.row1Content, + cmsPage.row1JustifyContent, + cmsPage.row1AlignItems + ), + row2: this.mapContentRow( + cmsPage.row2Content, + cmsPage.row2JustifyContent, + cmsPage.row2AlignItems + ), + row3: this.mapContentRow( + cmsPage.row3Content, + cmsPage.row3JustifyContent, + cmsPage.row3AlignItems + ), + }; + } + + static fromCmsArray(cmsPages: CmsPage[]): Page[] { + return cmsPages.map((page) => this.fromCms(page)); + } +} diff --git a/middlelayer/monitoring/README.md b/middlelayer/monitoring/README.md new file mode 100644 index 0000000..db9d887 --- /dev/null +++ b/middlelayer/monitoring/README.md @@ -0,0 +1,112 @@ +# Monitoring & Observability + +Der Middlelayer ist mit umfassendem Monitoring ausgestattet: + +## 1. Structured Logging (Winston) + +**Konfiguration:** +- Log-Level: `LOG_LEVEL` (default: `info`) +- Format: JSON in Production, farbig in Development +- Output: Console + Files (`logs/error.log`, `logs/combined.log`) + +**Verwendung:** +```typescript +import { logger, logQuery, logError } from './monitoring/logger.js'; + +logger.info('Info message', { context: 'data' }); +logQuery('GetProducts', { limit: 10 }, 45); +logError(error, { operation: 'getProducts' }); +``` + +## 2. Prometheus Metrics + +**Endpoints:** +- `GET http://localhost:9090/metrics` - Prometheus Metrics +- `GET http://localhost:9090/health` - Health Check + +**Verfügbare Metriken:** + +### Query Metrics +- `graphql_queries_total` - Anzahl der Queries (Labels: `operation`, `status`) +- `graphql_query_duration_seconds` - Query-Dauer (Histogram) +- `graphql_query_complexity` - Query-Komplexität (Gauge) + +### Cache Metrics +- `cache_hits_total` - Cache Hits (Label: `cache_type`) +- `cache_misses_total` - Cache Misses (Label: `cache_type`) + +### DataService Metrics +- `dataservice_calls_total` - DataService Aufrufe (Labels: `method`, `status`) +- `dataservice_duration_seconds` - DataService Dauer (Histogram) + +### Error Metrics +- `errors_total` - Anzahl der Fehler (Labels: `type`, `operation`) + +**Beispiel Prometheus Query:** +```promql +# Query Rate +rate(graphql_queries_total[5m]) + +# Error Rate +rate(errors_total[5m]) + +# Cache Hit Ratio +rate(cache_hits_total[5m]) / (rate(cache_hits_total[5m]) + rate(cache_misses_total[5m])) +``` + +## 3. Distributed Tracing + +**Features:** +- Automatische Trace-ID-Generierung pro Request +- Span-Tracking für verschachtelte Operationen +- Dauer-Messung für Performance-Analyse + +**Trace-IDs werden automatisch in Logs und Metrics eingebunden.** + +## Environment Variables + +```bash +# Logging +LOG_LEVEL=info # debug, info, warn, error + +# Metrics +METRICS_PORT=9090 # Port für Metrics-Endpoint + +# Query Complexity +MAX_QUERY_COMPLEXITY=1000 # Max. Query-Komplexität +``` + +## Integration mit Grafana + +**Prometheus Scrape Config:** +```yaml +scrape_configs: + - job_name: 'graphql-middlelayer' + static_configs: + - targets: ['localhost:9090'] +``` + +**Grafana Dashboard:** +- Importiere die Metriken in Grafana +- Erstelle Dashboards für: + - Query Performance + - Cache Hit Rates + - Error Rates + - Request Throughput + +## Beispiel-Dashboard Queries + +```promql +# Requests pro Sekunde +sum(rate(graphql_queries_total[1m])) by (operation) + +# Durchschnittliche Query-Dauer +avg(graphql_query_duration_seconds) by (operation) + +# Cache Hit Rate +sum(rate(cache_hits_total[5m])) / (sum(rate(cache_hits_total[5m])) + sum(rate(cache_misses_total[5m]))) + +# Error Rate +sum(rate(errors_total[5m])) by (type, operation) +``` + diff --git a/middlelayer/monitoring/logger.ts b/middlelayer/monitoring/logger.ts new file mode 100644 index 0000000..fcc231a --- /dev/null +++ b/middlelayer/monitoring/logger.ts @@ -0,0 +1,86 @@ +import winston from "winston"; + +/** + * Structured Logging mit Winston + * Erstellt JSON-Logs für bessere Analyse und Monitoring + */ +export const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || "info", + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: "graphql-middlelayer", + environment: process.env.NODE_ENV || "development", + }, + transports: [ + // Console Output (für Development) + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + const metaStr = Object.keys(meta).length + ? JSON.stringify(meta, null, 2) + : ""; + return `${timestamp} [${level}]: ${message} ${metaStr}`; + }) + ), + }), + // File Output (für Production) + ...(process.env.NODE_ENV === "production" + ? [ + new winston.transports.File({ + filename: "logs/error.log", + level: "error", + }), + new winston.transports.File({ + filename: "logs/combined.log", + }), + ] + : []), + ], +}); + +// Helper-Funktionen für strukturiertes Logging +export const logQuery = ( + operationName: string, + variables: any, + duration: number +) => { + logger.info("GraphQL Query executed", { + operation: operationName, + variables, + duration: `${duration}ms`, + type: "query", + }); +}; + +export const logError = (error: Error, context?: Record) => { + logger.error("Error occurred", { + error: { + message: error.message, + stack: error.stack, + name: error.name, + }, + ...context, + type: "error", + }); +}; + +export const logCacheHit = (key: string, type: string) => { + logger.debug("Cache hit", { + cacheKey: key, + cacheType: type, + type: "cache", + }); +}; + +export const logCacheMiss = (key: string, type: string) => { + logger.debug("Cache miss", { + cacheKey: key, + cacheType: type, + type: "cache", + }); +}; diff --git a/middlelayer/monitoring/metrics.ts b/middlelayer/monitoring/metrics.ts new file mode 100644 index 0000000..974afbd --- /dev/null +++ b/middlelayer/monitoring/metrics.ts @@ -0,0 +1,90 @@ +import { Registry, Counter, Histogram, Gauge } from 'prom-client'; + +/** + * Prometheus Metrics Registry + * Sammelt Metriken für Monitoring und Alerting + */ +export const register = new Registry(); + +// Default Metrics (CPU, Memory, etc.) +register.setDefaultLabels({ + app: 'graphql-middlelayer', +}); + +// Query Metrics +export const queryCounter = new Counter({ + name: 'graphql_queries_total', + help: 'Total number of GraphQL queries', + labelNames: ['operation', 'status'], + registers: [register], +}); + +export const queryDuration = new Histogram({ + name: 'graphql_query_duration_seconds', + help: 'Duration of GraphQL queries in seconds', + labelNames: ['operation'], + buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5], + registers: [register], +}); + +// Cache Metrics +export const cacheHits = new Counter({ + name: 'cache_hits_total', + help: 'Total number of cache hits', + labelNames: ['cache_type'], + registers: [register], +}); + +export const cacheMisses = new Counter({ + name: 'cache_misses_total', + help: 'Total number of cache misses', + labelNames: ['cache_type'], + registers: [register], +}); + +// DataService Metrics +export const dataServiceCalls = new Counter({ + name: 'dataservice_calls_total', + help: 'Total number of DataService calls', + labelNames: ['method', 'status'], + registers: [register], +}); + +export const dataServiceDuration = new Histogram({ + name: 'dataservice_duration_seconds', + help: 'Duration of DataService calls in seconds', + labelNames: ['method'], + buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1], + registers: [register], +}); + +// Error Metrics +export const errorCounter = new Counter({ + name: 'errors_total', + help: 'Total number of errors', + labelNames: ['type', 'operation'], + registers: [register], +}); + +// Active Connections +export const activeConnections = new Gauge({ + name: 'active_connections', + help: 'Number of active connections', + registers: [register], +}); + +// Query Complexity +export const queryComplexityGauge = new Gauge({ + name: 'graphql_query_complexity', + help: 'Complexity of GraphQL queries', + labelNames: ['operation'], + registers: [register], +}); + +/** + * Exportiert Metriken im Prometheus-Format + */ +export async function getMetrics(): Promise { + return register.metrics(); +} + diff --git a/middlelayer/monitoring/tracing.ts b/middlelayer/monitoring/tracing.ts new file mode 100644 index 0000000..43beba4 --- /dev/null +++ b/middlelayer/monitoring/tracing.ts @@ -0,0 +1,78 @@ +/** + * Einfaches Distributed Tracing + * Erstellt Trace-IDs für Request-Tracking + */ + +interface TraceContext { + traceId: string; + spanId: string; + parentSpanId?: string; + startTime: number; +} + +const traces = new Map(); + +/** + * Erstellt einen neuen Trace + */ +export function createTrace(traceId?: string): TraceContext { + const id = traceId || generateTraceId(); + const trace: TraceContext = { + traceId: id, + spanId: generateSpanId(), + startTime: Date.now(), + }; + traces.set(id, trace); + return trace; +} + +/** + * Erstellt einen Child-Span + */ +export function createSpan(traceId: string, parentSpanId?: string): string { + const trace = traces.get(traceId); + if (!trace) { + throw new Error(`Trace ${traceId} not found`); + } + + const spanId = generateSpanId(); + trace.parentSpanId = parentSpanId || trace.spanId; + + return spanId; +} + +/** + * Beendet einen Trace und gibt die Dauer zurück + */ +export function endTrace(traceId: string): number { + const trace = traces.get(traceId); + if (!trace) { + return 0; + } + + const duration = Date.now() - trace.startTime; + traces.delete(traceId); + return duration; +} + +/** + * Generiert eine Trace-ID + */ +function generateTraceId(): string { + return `trace-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; +} + +/** + * Generiert eine Span-ID + */ +function generateSpanId(): string { + return `span-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; +} + +/** + * Holt Trace-Informationen + */ +export function getTrace(traceId: string): TraceContext | undefined { + return traces.get(traceId); +} + diff --git a/middlelayer/plugins/monitoring.ts b/middlelayer/plugins/monitoring.ts new file mode 100644 index 0000000..2666cb9 --- /dev/null +++ b/middlelayer/plugins/monitoring.ts @@ -0,0 +1,101 @@ +import type { ApolloServerPlugin } from '@apollo/server'; +import { logger, logQuery, logError } from '../monitoring/logger.js'; +import { + queryCounter, + queryDuration, + errorCounter, + queryComplexityGauge, +} from '../monitoring/metrics.js'; +import { createTrace, endTrace } from '../monitoring/tracing.js'; + +/** + * Monitoring Plugin für Apollo Server + * Sammelt Logs, Metrics und Traces für jeden Request + */ +export const monitoringPlugin = (): ApolloServerPlugin => { + return { + async requestDidStart() { + return { + async didResolveOperation({ request, operationName }) { + // Erstelle Trace für Request + const traceId = createTrace().traceId; + (request as any).traceId = traceId; + + logger.info('GraphQL operation started', { + operationName: operationName || 'unknown', + query: request.query, + variables: request.variables, + traceId, + }); + }, + + async willSendResponse({ request, response }) { + const traceId = (request as any).traceId; + const operationName = request.operationName || 'unknown'; + const duration = traceId ? endTrace(traceId) : 0; + + // Log Query + logQuery(operationName, request.variables, duration); + + // Metrics + const status = response.errors && response.errors.length > 0 ? 'error' : 'success'; + queryCounter.inc({ operation: operationName, status }); + queryDuration.observe({ operation: operationName }, duration / 1000); + + // Log Errors + if (response.errors && response.errors.length > 0) { + response.errors.forEach((error) => { + logError(error as Error, { + operationName, + traceId, + }); + errorCounter.inc({ + type: error.extensions?.code as string || 'UNKNOWN', + operation: operationName, + }); + }); + } + }, + + async didEncounterErrors({ request, errors }) { + const traceId = (request as any).traceId; + const operationName = request.operationName || 'unknown'; + + errors.forEach((error) => { + logError(error as Error, { + operationName, + traceId, + }); + errorCounter.inc({ + type: error.extensions?.code as string || 'UNKNOWN', + operation: operationName, + }); + }); + }, + }; + }, + }; +}; + +/** + * Plugin für Query Complexity Tracking + */ +export const queryComplexityMonitoringPlugin = (): ApolloServerPlugin => { + return { + async requestDidStart() { + return { + async didResolveOperation({ request, operationName }) { + // Wird vom queryComplexityPlugin gesetzt + const complexity = (request as any).complexity; + if (complexity) { + queryComplexityGauge.set( + { operation: operationName || 'unknown' }, + complexity + ); + } + }, + }; + }, + }; +}; + diff --git a/middlelayer/plugins/queryComplexity.ts b/middlelayer/plugins/queryComplexity.ts new file mode 100644 index 0000000..f94a6dc --- /dev/null +++ b/middlelayer/plugins/queryComplexity.ts @@ -0,0 +1,67 @@ +import type { ApolloServerPlugin } from "@apollo/server"; +import { getComplexity, simpleEstimator } from "graphql-query-complexity"; +import { GraphQLError } from "graphql"; + +interface QueryComplexityPluginOptions { + maxComplexity?: number; + defaultComplexity?: number; +} + +/** + * Apollo Server Plugin für Query Complexity Limits + * Verhindert zu komplexe Queries, die das System überlasten könnten + */ +export const queryComplexityPlugin = ( + options: QueryComplexityPluginOptions = {} +): ApolloServerPlugin => { + const maxComplexity = options.maxComplexity ?? 1000; + const defaultComplexity = options.defaultComplexity ?? 1; + + return { + async requestDidStart() { + return { + async didResolveOperation({ request, document, schema }) { + if (!schema) return; + + try { + const complexity = getComplexity({ + schema, + operationName: request.operationName || undefined, + query: document, + variables: request.variables || {}, + estimators: [ + // Basis-Komplexität für jeden Field + simpleEstimator({ defaultComplexity }), + ], + }); + + // Speichere Complexity im Request für Monitoring + (request as any).complexity = complexity; + + if (complexity > maxComplexity) { + throw new GraphQLError( + `Query zu komplex (${complexity}). Maximum: ${maxComplexity}`, + { + extensions: { + code: "QUERY_TOO_COMPLEX", + complexity, + maxComplexity, + }, + } + ); + } + } catch (error: any) { + // Wenn es ein Schema-Realm-Problem gibt, logge es aber blockiere nicht + if (error.message?.includes("another module or realm")) { + console.warn( + "[Query Complexity] Schema-Realm-Konflikt, Complexity-Check übersprungen" + ); + return; + } + throw error; + } + }, + }; + }, + }; +}; diff --git a/middlelayer/plugins/responseCache.ts b/middlelayer/plugins/responseCache.ts new file mode 100644 index 0000000..55f8152 --- /dev/null +++ b/middlelayer/plugins/responseCache.ts @@ -0,0 +1,32 @@ +import ApolloServerPluginResponseCache from "@apollo/server-plugin-response-cache"; +import type { GraphQLRequestContext } from "@apollo/server"; + +/** + * Response Caching Plugin für Apollo Server + * Cached GraphQL Responses basierend auf Query und Variablen + */ +export const createResponseCachePlugin = () => { + return ApolloServerPluginResponseCache({ + // Session-ID für User-spezifisches Caching + sessionId: async ( + requestContext: GraphQLRequestContext + ): Promise => { + // Optional: User-ID aus Headers oder Context + const userId = requestContext.request.http?.headers.get("x-user-id"); + return userId || null; + }, + + // Cache nur bei erfolgreichen Queries + shouldWriteToCache: async ( + requestContext: GraphQLRequestContext + ): Promise => { + const query = requestContext.request.query; + + if (!query) return false; + + // Cache nur bestimmte Queries + const cacheableQueries = ["products", "pageSeo", "navigation", "page"]; + return cacheableQueries.some((q) => query.includes(q)); + }, + }); +}; diff --git a/middlelayer/resolvers.ts b/middlelayer/resolvers.ts new file mode 100644 index 0000000..c915947 --- /dev/null +++ b/middlelayer/resolvers.ts @@ -0,0 +1,207 @@ +import { dataService } from "./dataService.js"; +import { AdapterError, NotFoundError } from "./utils/errors.js"; +import type { GraphQLContext } from "./utils/dataloaders.js"; +import { userService } from "./auth/userService.js"; +import { logger } from "./monitoring/logger.js"; +import type { ContentItem } from "./types/page.js"; +import type { User } from "./types/user.js"; + +/** + * Fehlerbehandlung für GraphQL Resolver + */ +function handleError(error: unknown): never { + if (error instanceof NotFoundError) { + throw error; + } + if (error instanceof AdapterError) { + logger.error("Adapter-Fehler", { + message: error.message, + originalError: error.originalError, + }); + throw new Error(`Datenfehler: ${error.message}`); + } + logger.error("Unerwarteter Fehler", { error }); + throw new Error("Ein unerwarteter Fehler ist aufgetreten"); +} + +/** + * Wrapper-Funktion für GraphQL Resolver mit automatischem Error Handling + */ +async function withErrorHandling(resolver: () => Promise): Promise { + try { + return await resolver(); + } catch (error) { + handleError(error); + } +} + +/** + * Formatiert einen User für GraphQL (konvertiert Date zu ISO String) + */ +function formatUserForGraphQL(user: User) { + return { + ...user, + createdAt: user.createdAt.toISOString(), + }; +} + +export const resolvers = { + Query: { + products: async ( + _: unknown, + args: { limit?: number }, + context: GraphQLContext + ) => { + return withErrorHandling(() => dataService.getProducts(args.limit)); + }, + product: async ( + _: unknown, + args: { id: string }, + context: GraphQLContext + ) => { + return withErrorHandling(async () => { + // Verwende Dataloader für Batch-Loading + const product = await context.loaders.product.load(args.id); + if (!product) { + throw new NotFoundError("Produkt", args.id); + } + return product; + }); + }, + pageSeo: async ( + _: unknown, + args: { locale?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(() => dataService.getPageSeo(args.locale)); + }, + page: async ( + _: unknown, + args: { slug: string; locale?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(async () => { + // Verwende Dataloader für Batch-Loading (mit Locale im Key) + // Format: "slug:locale" oder "slug" (ohne "page:" Präfix) + const cacheKey = args.locale + ? `${args.slug}:${args.locale}` + : args.slug; + const page = await context.loaders.page.load(cacheKey); + if (!page) { + throw new NotFoundError("Seite", args.slug); + } + return page; + }); + }, + pages: async ( + _: unknown, + args: { locale?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(() => dataService.getPages(args.locale)); + }, + homepage: async ( + _: unknown, + args: { locale?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(async () => { + // Homepage ist immer die Seite mit dem Slug "/" + const homepage = await dataService.getPage("/", args.locale); + if (!homepage) { + throw new NotFoundError("Homepage", "/"); + } + return homepage; + }); + }, + navigation: async ( + _: unknown, + args: { locale?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(() => dataService.getNavigation(args.locale)); + }, + me: async (_: unknown, __: unknown, context: GraphQLContext) => { + return withErrorHandling(async () => { + // Gibt aktuellen User zurück (null wenn nicht authentifiziert) + if (!context.user) { + return null; + } + return formatUserForGraphQL(context.user); + }); + }, + translations: async ( + _: unknown, + args: { locale?: string; namespace?: string }, + context: GraphQLContext + ) => { + return withErrorHandling(() => + dataService.getTranslations(args.locale, args.namespace) + ); + }, + }, + Mutation: { + register: async ( + _: unknown, + args: { email: string; password: string; name: string }, + context: GraphQLContext + ) => { + return withErrorHandling(async () => { + const result = await userService.register({ + email: args.email, + password: args.password, + name: args.name, + }); + + logger.info("User registered via GraphQL", { + userId: result.user.id, + email: result.user.email, + }); + + return { + user: formatUserForGraphQL(result.user), + token: result.token, + }; + }); + }, + login: async ( + _: unknown, + args: { email: string; password: string }, + context: GraphQLContext + ) => { + return withErrorHandling(async () => { + const result = await userService.login({ + email: args.email, + password: args.password, + }); + + logger.info("User logged in via GraphQL", { + userId: result.user.id, + email: result.user.email, + }); + + return { + user: formatUserForGraphQL(result.user), + token: result.token, + }; + }); + }, + }, + ContentItem: { + __resolveType(obj: ContentItem): string | null { + // Map für Type-Resolution (Strategy Pattern) + const typeMap: Record = { + html: "HTMLContent", + markdown: "MarkdownContent", + iframe: "IframeContent", + imageGallery: "ImageGalleryContent", + image: "ImageContent", + quote: "QuoteContent", + youtubeVideo: "YoutubeVideoContent", + headline: "HeadlineContent", + }; + + return typeMap[obj.type] || null; + }, + }, +}; diff --git a/middlelayer/schema.ts b/middlelayer/schema.ts new file mode 100644 index 0000000..50d3da2 --- /dev/null +++ b/middlelayer/schema.ts @@ -0,0 +1,217 @@ +export const typeDefs = `#graphql + type ProductPromotion { + category: String! + text: String! + } + + type Product { + id: ID! + name: String! + description: String + price: Float! + originalPrice: Float + currency: String! + imageUrl: String + category: String + inStock: Boolean! + promotion: ProductPromotion + } + + type PageSeo { + title: String! + description: String! + metaRobotsIndex: String + metaRobotsFollow: String + } + + type contentLayout { + mobile: String! + tablet: String + desktop: String + spaceBottom: Float + } + + type HTMLContent { + type: String! + name: String! + html: String! + layout: contentLayout! + } + + type MarkdownContent { + type: String! + name: String! + content: String! + layout: contentLayout! + alignment: String! + } + + type IframeContent { + type: String! + name: String! + content: String! + iframe: String! + overlayImageUrl: String + layout: contentLayout! + } + + type ImageGalleryContent { + type: String! + name: String! + images: [ImageGalleryImage!]! + description: String + layout: contentLayout! + } + + type ImageGalleryImage { + url: String! + title: String + description: String + } + + type ImageContent { + type: String! + name: String! + imageUrl: String! + caption: String! + maxWidth: Float + aspectRatio: Float + layout: contentLayout! + } + + type QuoteContent { + type: String! + quote: String! + author: String! + variant: String! + layout: contentLayout! + } + + type YoutubeVideoContent { + type: String! + id: String! + youtubeId: String! + params: String + title: String + description: String + layout: contentLayout! + } + + type HeadlineContent { + type: String! + internal: String! + text: String! + tag: String! + align: String + layout: contentLayout! + } + + union ContentItem = HTMLContent | MarkdownContent | IframeContent | ImageGalleryContent | ImageContent | QuoteContent | YoutubeVideoContent | HeadlineContent + + type ContentRow { + justifyContent: String! + alignItems: String! + content: [ContentItem!]! + } + + type Page { + slug: String! + name: String! + linkName: String! + headline: String! + subheadline: String! + seoTitle: String! + seoMetaRobots: String! + seoDescription: String! + topFullwidthBanner: FullwidthBanner + row1: ContentRow + row2: ContentRow + row3: ContentRow + } + + type FullwidthBanner { + name: String! + variant: String! + headline: String! + subheadline: String! + text: String! + imageUrl: String + } + + type NavigationLink { + slug: String + name: String! + linkName: String! + url: String + icon: String + newTab: Boolean + } + + type Navigation { + name: String! + internal: String! + links: [NavigationLink!]! + } + + enum UserRole { + ADMIN + CUSTOMER + GUEST + } + + type User { + id: ID! + email: String! + name: String! + role: UserRole! + createdAt: String! + } + + type AuthResponse { + user: User! + token: String! + } + + type Translation { + key: String! + value: String! + namespace: String + } + + type Translations { + locale: String! + translations: [Translation!]! + } + + type Query { + products(limit: Int): [Product!]! + product(id: ID!): Product + pageSeo(locale: String): PageSeo! + page(slug: String!, locale: String): Page + pages(locale: String): [Page!]! + homepage(locale: String): Page + navigation(locale: String): Navigation! + me: User + translations(locale: String!, namespace: String): Translations! + } + + type Mutation { + register(email: String!, password: String!, name: String!): AuthResponse! + login(email: String!, password: String!): AuthResponse! + } + + type __Type { + kind: __TypeKind! + } + + enum __TypeKind { + SCALAR + OBJECT + INTERFACE + UNION + ENUM + INPUT_OBJECT + LIST + NON_NULL + } +`; diff --git a/middlelayer/types/README.md b/middlelayer/types/README.md new file mode 100644 index 0000000..5454b16 --- /dev/null +++ b/middlelayer/types/README.md @@ -0,0 +1,98 @@ +# Type Structure + +## Übersicht + +Die Typen sind in zwei Kategorien aufgeteilt: + +### 1. CMS-Typen (`types/cms/`) +**Zweck:** Struktur, wie Daten vom CMS (Contentful) kommen + +- `*Skeleton` - Wrapper mit `contentTypeId` und `fields` +- Verwendet `ComponentLayout` (ist jetzt ein Alias für `contentLayout`) +- Beispiel: `ComponentHeadlineSkeleton`, `HTMLSkeleton`, `MarkdownSkeleton` + +**Verwendung:** Nur im Mapper (`mappers/pageMapper.ts`) und Mock-Daten (`adapters/Mock/_cms/`) + +**Dateien:** +- `Headline.ts`, `Html.ts`, `Markdown.ts`, `Iframe.ts`, `ImageGallery.ts`, `Image.ts`, `Quote.ts`, `YoutubeVideo.ts` +- `Page.ts`, `Navigation.ts`, `SEO.ts`, `FullwidthBanner.ts` +- `Layout.ts` - `ComponentLayout` (Alias für `contentLayout`) +- `ContentType.enum.ts` - Enum für alle Content-Typen + +### 2. Domain-Typen (`types/c_*.ts`, `types/page.ts`, etc.) +**Zweck:** Struktur, wie Daten in der App verwendet werden + +- `c_*` - Content-Item-Typen mit `type`-Feld für Discriminated Union +- Verwendet `contentLayout` direkt +- Beispiel: `c_headline`, `c_html`, `c_markdown`, `c_iframe`, `c_imageGallery`, `c_image`, `c_quote`, `c_youtubeVideo` + +**Verwendung:** Überall in der App (GraphQL Schema, Astro Components, etc.) + +**Dateien:** +- `c_*.ts` - Alle Content-Item-Typen +- `page.ts` - Page-Typ mit `ContentItem` Union und `ContentRow` +- `contentLayout.ts` - Einheitlicher Layout-Typ +- `pageSeo.ts`, `navigation.ts`, `product.ts`, `user.ts` - Weitere Domain-Typen + +## Mapping + +Der `PageMapper` konvertiert von CMS-Typen zu Domain-Typen: + +```typescript +// Beispiel: Headline +CMS: ComponentHeadlineSkeleton { + contentTypeId: ContentType.headline, + fields: { + internal: string, + text: string, + tag: "h1" | "h2" | ..., + align?: "left" | "center" | "right", + layout: ComponentLayout + } +} +↓ (PageMapper.mapContentItem) +Domain: c_headline { + type: "headline", + internal: string, + text: string, + tag: "h1" | "h2" | ..., + align?: "left" | "center" | "right", + layout: contentLayout +} +``` + +## Layout-Typen + +- `ComponentLayout` (in `types/cms/Layout.ts`) = Alias für `contentLayout` +- `contentLayout` (in `types/contentLayout.ts`) = Einheitlicher Layout-Typ für alle Content-Items + +**Struktur:** +```typescript +{ + mobile: string; // z.B. "12", "6", "4" + tablet?: string; // Optional, z.B. "8", "6" + desktop?: string; // Optional, z.B. "6", "4" + spaceBottom?: number; // Optional, z.B. 0, 0.5, 1, 1.5, 2 +} +``` + +**Warum:** Redundanz vermeiden - beide haben die gleiche Struktur. `ComponentLayout` ist nur ein Alias, um die Semantik klar zu machen (CMS vs Domain). + +## Content-Items Union + +Alle Content-Items werden in `types/page.ts` zu einer Union zusammengefasst: + +```typescript +export type ContentItem = + | c_html + | c_markdown + | c_iframe + | c_imageGallery + | c_image + | c_quote + | c_youtubeVideo + | c_headline; +``` + +Dies ermöglicht Type-Safe Discriminated Unions über das `type`-Feld. + diff --git a/middlelayer/types/c_headline.ts b/middlelayer/types/c_headline.ts new file mode 100644 index 0000000..618c30e --- /dev/null +++ b/middlelayer/types/c_headline.ts @@ -0,0 +1,10 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_headline = { + type: "headline"; + internal: string; + text: string; + tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + align?: "left" | "center" | "right"; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_html.ts b/middlelayer/types/c_html.ts new file mode 100644 index 0000000..f4813a8 --- /dev/null +++ b/middlelayer/types/c_html.ts @@ -0,0 +1,8 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_html = { + type: "html"; + name: string; + html: string; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_iframe.ts b/middlelayer/types/c_iframe.ts new file mode 100644 index 0000000..695065e --- /dev/null +++ b/middlelayer/types/c_iframe.ts @@ -0,0 +1,10 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_iframe = { + type: "iframe"; + name: string; + content: string; + iframe: string; + overlayImageUrl?: string; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_image.ts b/middlelayer/types/c_image.ts new file mode 100644 index 0000000..846fdf8 --- /dev/null +++ b/middlelayer/types/c_image.ts @@ -0,0 +1,11 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_image = { + type: "image"; + name: string; + imageUrl: string; + caption: string; + maxWidth?: number; + aspectRatio?: number; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_imageGallery.ts b/middlelayer/types/c_imageGallery.ts new file mode 100644 index 0000000..350039f --- /dev/null +++ b/middlelayer/types/c_imageGallery.ts @@ -0,0 +1,13 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_imageGallery = { + type: "imageGallery"; + name: string; + images: Array<{ + url: string; + title?: string; + description?: string; + }>; + description?: string; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_markdown.ts b/middlelayer/types/c_markdown.ts new file mode 100644 index 0000000..1098027 --- /dev/null +++ b/middlelayer/types/c_markdown.ts @@ -0,0 +1,9 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_markdown = { + type: "markdown"; + name: string; + content: string; + layout: contentLayout; + alignment: "left" | "center" | "right"; +}; diff --git a/middlelayer/types/c_quote.ts b/middlelayer/types/c_quote.ts new file mode 100644 index 0000000..24184a2 --- /dev/null +++ b/middlelayer/types/c_quote.ts @@ -0,0 +1,9 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_quote = { + type: "quote"; + quote: string; + author: string; + variant: "left" | "right"; + layout: contentLayout; +}; diff --git a/middlelayer/types/c_youtubeVideo.ts b/middlelayer/types/c_youtubeVideo.ts new file mode 100644 index 0000000..a43a7bc --- /dev/null +++ b/middlelayer/types/c_youtubeVideo.ts @@ -0,0 +1,11 @@ +import type { contentLayout } from "./contentLayout"; + +export type c_youtubeVideo = { + type: "youtubeVideo"; + id: string; + youtubeId: string; + params?: string; + title?: string; + description?: string; + layout: contentLayout; +}; diff --git a/middlelayer/types/cms/CloudinaryImage.ts b/middlelayer/types/cms/CloudinaryImage.ts new file mode 100644 index 0000000..4579ee3 --- /dev/null +++ b/middlelayer/types/cms/CloudinaryImage.ts @@ -0,0 +1,16 @@ +export interface CloudinaryImage { + bytes: number; + created_at: string; + format: string; + height: number; + original_secure_url: string; + original_url: string; + public_id: string; + resource_type: string; + secure_url: string; + type: string; + url: string; + version: number; + width: number; +} + diff --git a/middlelayer/types/cms/Content.ts b/middlelayer/types/cms/Content.ts new file mode 100644 index 0000000..47cd775 --- /dev/null +++ b/middlelayer/types/cms/Content.ts @@ -0,0 +1,42 @@ +import type { HTMLSkeleton } from "./Html"; +import type { MarkdownSkeleton } from "./Markdown"; +import type { ComponentIframeSkeleton } from "./Iframe"; +import type { ImageGallerySkeleton } from "./ImageGallery"; +import type { ComponentImageSkeleton } from "./Image"; +import type { QuoteSkeleton } from "./Quote"; +import type { ComponentYoutubeVideoSkeleton } from "./YoutubeVideo"; +import type { ComponentHeadlineSkeleton } from "./Headline"; + +export type rowJutify = + | "start" + | "end" + | "center" + | "between" + | "around" + | "evenly"; +export type rowAlignItems = "start" | "end" | "center" | "baseline" | "stretch"; + +export type ContentEntry = + | HTMLSkeleton + | MarkdownSkeleton + | ComponentIframeSkeleton + | ImageGallerySkeleton + | ComponentImageSkeleton + | QuoteSkeleton + | ComponentYoutubeVideoSkeleton + | ComponentHeadlineSkeleton; + +export interface Content { + row1JustifyContent: rowJutify; + row1AlignItems: rowAlignItems; + row1Content: ContentEntry[]; + + row2JustifyContent: rowJutify; + row2AlignItems: rowAlignItems; + row2Content: ContentEntry[]; + + row3JustifyContent: rowJutify; + row3AlignItems: rowAlignItems; + row3Content: ContentEntry[]; +} + diff --git a/middlelayer/types/cms/ContentType.enum.ts b/middlelayer/types/cms/ContentType.enum.ts new file mode 100644 index 0000000..4c153d4 --- /dev/null +++ b/middlelayer/types/cms/ContentType.enum.ts @@ -0,0 +1,32 @@ +export enum ContentType { + "componentLinkList" = "componentLinkList", + "badges" = "badges", + "componentPostOverview" = "componentPostOverview", + "footer" = "footer", + "fullwidthBanner" = "fullwidthBanner", + "headline" = "headline", + "html" = "html", + "image" = "image", + "img" = "img", + "iframe" = "iframe", + "imgGallery" = "imageGallery", + "internalReference" = "internalComponent", + "link" = "link", + "list" = "list", + "markdown" = "markdown", + "navigation" = "navigation", + "page" = "page", + "pageConfig" = "pageConfig", + "picture" = "picture", + "post" = "post", + "postComponent" = "postComponent", + "quote" = "quoteComponent", + "richtext" = "richtext", + "row" = "row", + "rowLayout" = "rowLayout", + "tag" = "tag", + "youtubeVideo" = "youtubeVideo", + "campaign" = "campaign", + "campaigns" = "campaigns", +} + diff --git a/middlelayer/types/cms/FullwidthBanner.ts b/middlelayer/types/cms/FullwidthBanner.ts new file mode 100644 index 0000000..8148878 --- /dev/null +++ b/middlelayer/types/cms/FullwidthBanner.ts @@ -0,0 +1,24 @@ +import type { ComponentImgSkeleton } from "./Img"; +import type { CloudinaryImage } from "./CloudinaryImage"; +import type { ContentType } from "./ContentType.enum"; + +export enum FullwidthBannerVariant { + "dark" = "dark", + "light" = "light", +} + +export interface FullwidthBanner { + name: string; + variant: FullwidthBannerVariant; + headline: string; + subheadline: string; + text: string; + image: CloudinaryImage[]; + img: ComponentImgSkeleton; +} + +export type FullwidthBannerSkeleton = { + contentTypeId: ContentType.fullwidthBanner; + fields: FullwidthBanner; +}; + diff --git a/middlelayer/types/cms/Headline.ts b/middlelayer/types/cms/Headline.ts new file mode 100644 index 0000000..c3294e6 --- /dev/null +++ b/middlelayer/types/cms/Headline.ts @@ -0,0 +1,21 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentLayout } from "./Layout"; + +export type Component_Headline_Align = "left" | "center" | "right"; + +export type Component_Headline_Tag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + +export type alignTextClasses = "text-left" | "text-center" | "text-right"; + +export interface ComponentHeadline { + internal: string; + text: string; + tag: Component_Headline_Tag; + layout: ComponentLayout; + align?: Component_Headline_Align; +} + +export interface ComponentHeadlineSkeleton { + contentTypeId: ContentType.headline; + fields: ComponentHeadline; +} diff --git a/middlelayer/types/cms/Html.ts b/middlelayer/types/cms/Html.ts new file mode 100644 index 0000000..803ff41 --- /dev/null +++ b/middlelayer/types/cms/Html.ts @@ -0,0 +1,14 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentLayout } from "./Layout"; + +export interface HTML { + id: string; + html: string; + layout: ComponentLayout; +} + +export type HTMLSkeleton = { + contentTypeId: ContentType.html; + fields: HTML; +}; + diff --git a/middlelayer/types/cms/Iframe.ts b/middlelayer/types/cms/Iframe.ts new file mode 100644 index 0000000..56d0da0 --- /dev/null +++ b/middlelayer/types/cms/Iframe.ts @@ -0,0 +1,17 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentImgSkeleton } from "./Img"; +import type { ComponentLayout } from "./Layout"; + +export interface ComponentIframe { + name: string; + content: string; + iframe: string; + overlayImage?: ComponentImgSkeleton; + layout: ComponentLayout; +} + +export interface ComponentIframeSkeleton { + contentTypeId: ContentType.iframe; + fields: ComponentIframe; +} + diff --git a/middlelayer/types/cms/Image.ts b/middlelayer/types/cms/Image.ts new file mode 100644 index 0000000..cd7f065 --- /dev/null +++ b/middlelayer/types/cms/Image.ts @@ -0,0 +1,18 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentImgSkeleton } from "./Img"; +import type { ComponentLayout } from "./Layout"; + +export interface ComponentImage { + name: string; + image: ComponentImgSkeleton; + caption: string; + layout: ComponentLayout; + maxWidth?: number; + aspectRatio?: number; +} + +export interface ComponentImageSkeleton { + contentTypeId: ContentType.image; + fields: ComponentImage; +} + diff --git a/middlelayer/types/cms/ImageGallery.ts b/middlelayer/types/cms/ImageGallery.ts new file mode 100644 index 0000000..fd9ab31 --- /dev/null +++ b/middlelayer/types/cms/ImageGallery.ts @@ -0,0 +1,16 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentImgSkeleton } from "./Img"; +import type { ComponentLayout } from "./Layout"; + +export interface ImageGallery { + name: string; + images: ComponentImgSkeleton[]; + layout: ComponentLayout; + description?: string; +} + +export interface ImageGallerySkeleton { + contentTypeId: ContentType.imgGallery; + fields: ImageGallery; +} + diff --git a/middlelayer/types/cms/Img.ts b/middlelayer/types/cms/Img.ts new file mode 100644 index 0000000..70486b1 --- /dev/null +++ b/middlelayer/types/cms/Img.ts @@ -0,0 +1,26 @@ +import type { ContentType } from "./ContentType.enum"; + +export interface ComponentImgDetails { + size: number; + image: { + width: number; + height: number; + }; +} + +export interface ComponentImg { + title: string; + description: string; + file: { + url: string; + details: ComponentImgDetails; + fileName: string; + contentType: string; + }; +} + +export interface ComponentImgSkeleton { + contentTypeId: ContentType.img; + fields: ComponentImg; +} + diff --git a/middlelayer/types/cms/Layout.ts b/middlelayer/types/cms/Layout.ts new file mode 100644 index 0000000..5b878b4 --- /dev/null +++ b/middlelayer/types/cms/Layout.ts @@ -0,0 +1,13 @@ +import type { ContentType } from "./ContentType.enum"; +import type { contentLayout } from "../contentLayout"; + +/** + * CMS-spezifisches Layout (wird vom Mapper zu contentLayout konvertiert) + * Verwendet contentLayout direkt, um Redundanz zu vermeiden + */ +export type ComponentLayout = contentLayout; + +export interface ComponentLayoutSkeleton { + contentTypeId: ContentType.rowLayout; + fields: ComponentLayout; +} diff --git a/middlelayer/types/cms/Link.ts b/middlelayer/types/cms/Link.ts new file mode 100644 index 0000000..7cf5f0c --- /dev/null +++ b/middlelayer/types/cms/Link.ts @@ -0,0 +1,9 @@ +export interface Link { + name: string; + internal: string; + linkName: string; + url: string; + icon?: string; + newTab?: boolean; +} + diff --git a/middlelayer/types/cms/Markdown.ts b/middlelayer/types/cms/Markdown.ts new file mode 100644 index 0000000..23341d4 --- /dev/null +++ b/middlelayer/types/cms/Markdown.ts @@ -0,0 +1,16 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentLayout } from "./Layout"; +import type { TextAlignment } from "./TextAlignment"; + +export interface Markdown { + name: string; + content: string; + layout: ComponentLayout; + alignment: TextAlignment; +} + +export type MarkdownSkeleton = { + contentTypeId: ContentType.markdown; + fields: Markdown; +}; + diff --git a/middlelayer/types/cms/Navigation.ts b/middlelayer/types/cms/Navigation.ts new file mode 100644 index 0000000..efbd211 --- /dev/null +++ b/middlelayer/types/cms/Navigation.ts @@ -0,0 +1,15 @@ +import type { ContentType } from "./ContentType.enum"; +import type { Link } from "./Link"; +import type { Page } from "./Page"; + +export interface Navigation { + name: string; + internal: string; + links: Array<{ fields: Link | Page }>; +} + +export interface NavigationSkeleton { + contentTypeId: ContentType.navigation; + fields: Navigation; +} + diff --git a/middlelayer/types/cms/Page.ts b/middlelayer/types/cms/Page.ts new file mode 100644 index 0000000..f5811ac --- /dev/null +++ b/middlelayer/types/cms/Page.ts @@ -0,0 +1,20 @@ +import type { ContentType } from "./ContentType.enum"; +import type { FullwidthBannerSkeleton } from "./FullwidthBanner"; +import type { Content } from "./Content"; +import type { SEO } from "./SEO"; + +export interface Page extends Content, SEO { + slug: string; + name: string; + linkName: string; + icon?: string; + headline: string; + subheadline: string; + topFullwidthBanner: FullwidthBannerSkeleton; +} + +export type PageSkeleton = { + contentTypeId: ContentType.page; + fields: Page; +}; + diff --git a/middlelayer/types/cms/PageConfig.ts b/middlelayer/types/cms/PageConfig.ts new file mode 100644 index 0000000..91f0bb0 --- /dev/null +++ b/middlelayer/types/cms/PageConfig.ts @@ -0,0 +1,19 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentImgSkeleton } from "./Img"; + +export interface PageConfig { + logo: ComponentImgSkeleton; + footerText1: string; + seoTitle: string; + seoDescription: string; + blogTagPageHeadline: string; + blogPostsPageHeadline: string; + blogPostsPageSubHeadline: string; + website: string; +} + +export interface PageConfigSkeleton { + contentTypeId: ContentType.pageConfig; + fields: PageConfig; +} + diff --git a/middlelayer/types/cms/Quote.ts b/middlelayer/types/cms/Quote.ts new file mode 100644 index 0000000..ee85af7 --- /dev/null +++ b/middlelayer/types/cms/Quote.ts @@ -0,0 +1,15 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentLayout } from "./Layout"; + +export interface Quote { + quote: string; + author: string; + variant: "left" | "right"; + layout: ComponentLayout; +} + +export type QuoteSkeleton = { + contentTypeId: ContentType.quote; + fields: Quote; +}; + diff --git a/middlelayer/types/cms/SEO.ts b/middlelayer/types/cms/SEO.ts new file mode 100644 index 0000000..2520591 --- /dev/null +++ b/middlelayer/types/cms/SEO.ts @@ -0,0 +1,12 @@ +export type metaRobots = + | "index, follow" + | "noindex, follow" + | "index, nofollow" + | "noindex, nofollow"; + +export interface SEO { + seoTitle: string; + seoMetaRobots: metaRobots; + seoDescription: string; +} + diff --git a/middlelayer/types/cms/TextAlignment.ts b/middlelayer/types/cms/TextAlignment.ts new file mode 100644 index 0000000..53c5f18 --- /dev/null +++ b/middlelayer/types/cms/TextAlignment.ts @@ -0,0 +1,7 @@ +export type TextAlignment = "left" | "center" | "right"; +export enum TextAlignmentClasses { + "left" = "text-left", + "center" = "text-center", + "right" = "text-right", +} + diff --git a/middlelayer/types/cms/YoutubeVideo.ts b/middlelayer/types/cms/YoutubeVideo.ts new file mode 100644 index 0000000..47898ac --- /dev/null +++ b/middlelayer/types/cms/YoutubeVideo.ts @@ -0,0 +1,17 @@ +import type { ContentType } from "./ContentType.enum"; +import type { ComponentLayout } from "./Layout"; + +export interface YoutubeVideo { + id: string; + youtubeId: string; + params?: string; + title?: string; + description?: string; + layout: ComponentLayout; +} + +export interface ComponentYoutubeVideoSkeleton { + contentTypeId: ContentType.youtubeVideo; + fields: YoutubeVideo; +} + diff --git a/middlelayer/types/cms/index.ts b/middlelayer/types/cms/index.ts new file mode 100644 index 0000000..b1def89 --- /dev/null +++ b/middlelayer/types/cms/index.ts @@ -0,0 +1,22 @@ +// Re-export all types for easier imports +export * from "./ContentType.enum"; +export * from "./Layout"; +export * from "./Html"; +export * from "./Markdown"; +export * from "./Img"; +export * from "./Iframe"; +export * from "./ImageGallery"; +export * from "./Image"; +export * from "./Quote"; +export * from "./YoutubeVideo"; +export * from "./Headline"; +export * from "./FullwidthBanner"; +export * from "./Content"; +export * from "./SEO"; +export * from "./Page"; +export * from "./TextAlignment"; +export * from "./CloudinaryImage"; +export * from "./Navigation"; +export * from "./Link"; +export * from "./PageConfig"; + diff --git a/middlelayer/types/contentLayout.ts b/middlelayer/types/contentLayout.ts new file mode 100644 index 0000000..bbe63c7 --- /dev/null +++ b/middlelayer/types/contentLayout.ts @@ -0,0 +1,6 @@ +export type contentLayout = { + mobile: string; + tablet?: string; + desktop?: string; + spaceBottom?: number; +}; diff --git a/middlelayer/types/index.ts b/middlelayer/types/index.ts new file mode 100644 index 0000000..22a778a --- /dev/null +++ b/middlelayer/types/index.ts @@ -0,0 +1,4 @@ +export type { PageSeo } from "./pageSeo"; +export type { Page } from "./page"; +export type { Navigation, NavigationLink } from "./navigation"; +export type { Product } from "./product"; diff --git a/middlelayer/types/navigation.ts b/middlelayer/types/navigation.ts new file mode 100644 index 0000000..caccfd6 --- /dev/null +++ b/middlelayer/types/navigation.ts @@ -0,0 +1,15 @@ +export interface NavigationLink { + slug?: string; + name: string; + linkName: string; + url?: string; + icon?: string; + newTab?: boolean; +} + +export interface Navigation { + name: string; + internal: string; + links: NavigationLink[]; +} + diff --git a/middlelayer/types/page.ts b/middlelayer/types/page.ts new file mode 100644 index 0000000..7630774 --- /dev/null +++ b/middlelayer/types/page.ts @@ -0,0 +1,48 @@ +import type { contentLayout } from "./contentLayout"; +import type { c_html } from "./c_html"; +import type { c_markdown } from "./c_markdown"; +import type { c_iframe } from "./c_iframe"; +import type { c_imageGallery } from "./c_imageGallery"; +import type { c_image } from "./c_image"; +import type { c_quote } from "./c_quote"; +import type { c_youtubeVideo } from "./c_youtubeVideo"; +import type { c_headline } from "./c_headline"; + +export type { contentLayout }; +export type ContentItem = + | c_html + | c_markdown + | c_iframe + | c_imageGallery + | c_image + | c_quote + | c_youtubeVideo + | c_headline; + +export type ContentRow = { + justifyContent: "start" | "end" | "center" | "between" | "around" | "evenly"; + alignItems: "start" | "end" | "center" | "baseline" | "stretch"; + content: ContentItem[]; +}; + +export interface Page { + slug: string; + name: string; + linkName: string; + headline: string; + subheadline: string; + seoTitle: string; + seoMetaRobots: string; + seoDescription: string; + topFullwidthBanner?: { + name: string; + variant: string; + headline: string; + subheadline: string; + text: string; + imageUrl?: string; + }; + row1?: ContentRow; + row2?: ContentRow; + row3?: ContentRow; +} diff --git a/middlelayer/types/pageSeo.ts b/middlelayer/types/pageSeo.ts new file mode 100644 index 0000000..da9b7ae --- /dev/null +++ b/middlelayer/types/pageSeo.ts @@ -0,0 +1,7 @@ +export interface PageSeo { + title: string; + description: string; + metaRobotsIndex?: string; + metaRobotsFollow?: string; +} + diff --git a/middlelayer/types/product.ts b/middlelayer/types/product.ts new file mode 100644 index 0000000..06e0e78 --- /dev/null +++ b/middlelayer/types/product.ts @@ -0,0 +1,17 @@ +export type ProductPromotion = { + category: "sale" | "topseller"; + text: string; // z.B. "-30%", "top" +}; + +export type Product = { + id: string; + name: string; + description?: string; + price: number; + originalPrice?: number; // Streichpreis + currency: string; + imageUrl?: string; + category?: string; + inStock: boolean; + promotion?: ProductPromotion; +}; diff --git a/middlelayer/types/user.ts b/middlelayer/types/user.ts new file mode 100644 index 0000000..8beda83 --- /dev/null +++ b/middlelayer/types/user.ts @@ -0,0 +1,45 @@ +/** + * User-Rollen für Authorization + */ +export enum UserRole { + ADMIN = "admin", + CUSTOMER = "customer", + GUEST = "guest", +} + +/** + * User-Interface + */ +export interface User { + id: string; + email: string; + name: string; + role: UserRole; + createdAt: Date; +} + +/** + * JWT Payload + */ +export interface JWTPayload { + userId: string; + email: string; + role: UserRole; +} + +/** + * Login Credentials + */ +export interface LoginCredentials { + email: string; + password: string; +} + +/** + * Register Data + */ +export interface RegisterData { + email: string; + password: string; + name: string; +} diff --git a/middlelayer/utils/REDIS_SETUP.md b/middlelayer/utils/REDIS_SETUP.md new file mode 100644 index 0000000..b32d2d2 --- /dev/null +++ b/middlelayer/utils/REDIS_SETUP.md @@ -0,0 +1,82 @@ +# Redis Cache Setup + +## Lokale Installation + +### macOS (mit Homebrew) +```bash +brew install redis +brew services start redis +``` + +### Docker +```bash +docker run -d -p 6379:6379 --name redis redis:latest +``` + +### Linux (Ubuntu/Debian) +```bash +sudo apt-get update +sudo apt-get install redis-server +sudo systemctl start redis +``` + +## Konfiguration + +### Environment Variables +```bash +# Redis aktivieren +REDIS_ENABLED=true + +# Redis-Verbindung (optional, Defaults: localhost:6379) +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= # Optional, nur wenn gesetzt +``` + +### Beispiel .env Datei +```env +REDIS_ENABLED=true +REDIS_HOST=localhost +REDIS_PORT=6379 +``` + +## Verwendung + +### Redis aktivieren +```bash +REDIS_ENABLED=true npm run mock:server +``` + +### Ohne Redis (In-Memory Fallback) +```bash +# REDIS_ENABLED nicht setzen oder auf false +npm run mock:server +``` + +## Features + +- ✅ **Automatischer Fallback**: Wenn Redis nicht verfügbar ist, wird automatisch In-Memory Cache verwendet +- ✅ **Retry-Logik**: Bei Verbindungsfehlern wird automatisch auf In-Memory umgeschaltet +- ✅ **Metrics**: Cache-Hits/Misses werden weiterhin getrackt +- ✅ **Logging**: Alle Redis-Operationen werden geloggt + +## Testen + +```bash +# Redis-Verbindung testen +redis-cli ping +# Sollte "PONG" zurückgeben + +# Cache-Keys anzeigen +redis-cli keys "*" + +# Cache leeren +redis-cli FLUSHALL +``` + +## Monitoring + +Die Cache-Metriken sind weiterhin verfügbar unter: +- `http://localhost:9090/metrics` - Prometheus Metrics +- Logs zeigen Redis-Status und Fallback-Verhalten + diff --git a/middlelayer/utils/cache.ts b/middlelayer/utils/cache.ts new file mode 100644 index 0000000..c06cf59 --- /dev/null +++ b/middlelayer/utils/cache.ts @@ -0,0 +1,82 @@ +import { cacheConfig } from "../config/cache.js"; +import { cacheHits, cacheMisses } from "../monitoring/metrics.js"; +import { logCacheHit, logCacheMiss } from "../monitoring/logger.js"; +import { RedisCache } from "./redisCache.js"; + +/** + * Cache-Interface für Abstraktion + */ +export interface CacheInterface { + set(key: string, data: T, ttl?: number): void | Promise; + get(key: string): T | null | Promise; + clear(): void | Promise; + delete(key: string): void | Promise; +} + +/** + * In-Memory Cache (Fallback wenn Redis nicht verfügbar) + */ +class InMemoryCache implements CacheInterface { + private cache = new Map(); + private defaultTTL: number; + private cacheType: string; + + constructor(defaultTTL: number, cacheType: string) { + this.defaultTTL = defaultTTL; + this.cacheType = cacheType; + } + + async set(key: string, data: T, ttl?: number): Promise { + const expiresAt = Date.now() + (ttl || this.defaultTTL); + this.cache.set(key, { data, expiresAt }); + } + + async get(key: string): Promise { + const entry = this.cache.get(key); + if (!entry) { + cacheMisses.inc({ cache_type: this.cacheType }); + logCacheMiss(key, this.cacheType); + return null; + } + + if (Date.now() > entry.expiresAt) { + this.cache.delete(key); + cacheMisses.inc({ cache_type: this.cacheType }); + logCacheMiss(key, this.cacheType); + return null; + } + + cacheHits.inc({ cache_type: this.cacheType }); + logCacheHit(key, this.cacheType); + return entry.data; + } + + async clear(): Promise { + this.cache.clear(); + } + + async delete(key: string): Promise { + this.cache.delete(key); + } +} + +/** + * Cache-Instanzen + * Verwendet Redis wenn aktiviert, sonst In-Memory + */ +const useRedis = process.env.REDIS_ENABLED === "true"; + +export const cache = { + pages: useRedis + ? new RedisCache(cacheConfig.pages.ttl, "pages") + : new InMemoryCache(cacheConfig.pages.ttl, "pages"), + pageSeo: useRedis + ? new RedisCache(cacheConfig.pageSeo.ttl, "pageSeo") + : new InMemoryCache(cacheConfig.pageSeo.ttl, "pageSeo"), + navigation: useRedis + ? new RedisCache(cacheConfig.navigation.ttl, "navigation") + : new InMemoryCache(cacheConfig.navigation.ttl, "navigation"), + products: useRedis + ? new RedisCache(cacheConfig.products.ttl, "products") + : new InMemoryCache(cacheConfig.products.ttl, "products"), +}; diff --git a/middlelayer/utils/cacheKeys.ts b/middlelayer/utils/cacheKeys.ts new file mode 100644 index 0000000..bd88e77 --- /dev/null +++ b/middlelayer/utils/cacheKeys.ts @@ -0,0 +1,33 @@ +/** + * Zentralisierte Cache-Key-Generierung + * Stellt sicher, dass Cache-Keys konsistent und wartbar sind + */ +export class CacheKeyBuilder { + static page(slug: string, locale?: string): string { + return `page:${slug}:${locale || "default"}`; + } + + static pages(locale?: string): string { + return `pages:all:${locale || "default"}`; + } + + static pageSeo(locale?: string): string { + return `pageSeo:${locale || "default"}`; + } + + static navigation(locale?: string): string { + return `navigation:${locale || "default"}`; + } + + static product(id: string): string { + return `product:${id}`; + } + + static products(limit?: number): string { + return `products:${limit || "default"}`; + } + + static translations(locale: string, namespace?: string): string { + return `translations:${locale}${namespace ? `:${namespace}` : ""}`; + } +} diff --git a/middlelayer/utils/dataServiceHelpers.ts b/middlelayer/utils/dataServiceHelpers.ts new file mode 100644 index 0000000..2c58f73 --- /dev/null +++ b/middlelayer/utils/dataServiceHelpers.ts @@ -0,0 +1,80 @@ +import { + dataServiceCalls, + dataServiceDuration, +} from "../monitoring/metrics.js"; +import { logger } from "../monitoring/logger.js"; +import { AdapterError } from "./errors.js"; +import type { CacheInterface } from "./cache.js"; + +/** + * Helper für DataService-Methoden mit Cache und Metrics + */ +export class DataServiceHelpers { + /** + * Führt eine DataService-Operation mit Cache und Metrics aus + */ + static async withCacheAndMetrics( + method: string, + cache: CacheInterface, + cacheKey: string, + operation: () => Promise, + errorMessage: string, + context?: Record + ): Promise { + const startTime = Date.now(); + + // Prüfe Cache + const cached = await cache.get(cacheKey); + if (cached) { + dataServiceCalls.inc({ method, status: "success" }); + dataServiceDuration.observe({ method }, (Date.now() - startTime) / 1000); + return cached; + } + + try { + // Führe Operation aus + const result = await operation(); + + // Speichere im Cache (wenn Ergebnis vorhanden) + if (result) { + await cache.set(cacheKey, result); + } + + // Metrics + dataServiceCalls.inc({ method, status: "success" }); + dataServiceDuration.observe({ method }, (Date.now() - startTime) / 1000); + + return result; + } catch (error) { + // Error Metrics + dataServiceCalls.inc({ method, status: "error" }); + dataServiceDuration.observe({ method }, (Date.now() - startTime) / 1000); + + logger.error(`Error in ${method}`, { ...context, error }); + throw new AdapterError(errorMessage, error); + } + } + + /** + * Führt eine DataService-Operation nur mit Cache aus (ohne Metrics) + */ + static async withCache( + cache: CacheInterface, + cacheKey: string, + operation: () => Promise, + errorMessage: string + ): Promise { + const cached = await cache.get(cacheKey); + if (cached) { + return cached; + } + + try { + const result = await operation(); + await cache.set(cacheKey, result); + return result; + } catch (error) { + throw new AdapterError(errorMessage, error); + } + } +} diff --git a/middlelayer/utils/dataloaders.ts b/middlelayer/utils/dataloaders.ts new file mode 100644 index 0000000..52dcd14 --- /dev/null +++ b/middlelayer/utils/dataloaders.ts @@ -0,0 +1,89 @@ +import DataLoader from "dataloader"; +import type { Product, Page } from "../types/index.js"; +import type { User } from "../types/user.js"; +import { dataService } from "../dataService.js"; +import { userService } from "../auth/userService.js"; + +/** + * Dataloader für Batch-Loading von Produkten + * Verhindert N+1 Queries, wenn mehrere Produkte in einer Query abgefragt werden + */ +export const createProductLoader = () => { + return new DataLoader( + async (ids: readonly string[]) => { + // Batch-Loading: Lade alle Produkte in einem Batch + const products = await Promise.all( + ids.map((id) => dataService.getProduct(id)) + ); + + // Stelle sicher, dass die Reihenfolge mit den IDs übereinstimmt + return products; + } + ); +}; + +/** + * Dataloader für Batch-Loading von Seiten + * Unterstützt Locale im Key-Format: "slug:locale" oder "slug" + */ +export const createPageLoader = () => { + return new DataLoader( + async (keys: readonly string[]) => { + const pages = await Promise.all( + keys.map((key) => { + // Parse Key: Format kann "slug:locale" oder "slug" sein + const parts = key.split(":"); + const slug = parts[0]; + const locale = parts.length > 1 ? parts[1] : undefined; + return dataService.getPage(slug, locale); + }) + ); + return pages; + } + ); +}; + +/** + * Dataloader für Batch-Loading von Usern + */ +export const createUserLoader = () => { + return new DataLoader( + async (userIds: readonly string[]) => { + const users = await Promise.all( + userIds.map((id) => userService.getUserById(id)) + ); + return users; + } + ); +}; + +/** + * Context für GraphQL Resolver + * Enthält Dataloader-Instanzen und User-Informationen + * + * Der Context wird für jeden GraphQL Request erstellt und enthält: + * - `user`: Aktueller authentifizierter User (oder null) + * - `loaders`: Dataloader-Instanzen für Batch-Loading (verhindert N+1 Queries) + */ +export interface GraphQLContext { + user: User | null; + loaders: { + product: DataLoader; + page: DataLoader; + user: DataLoader; + }; +} + +/** + * Erstellt einen neuen Context mit Dataloadern und User + */ +export const createContext = (user: User | null = null): GraphQLContext => { + return { + user, + loaders: { + product: createProductLoader(), + page: createPageLoader(), + user: createUserLoader(), + }, + }; +}; diff --git a/middlelayer/utils/errors.ts b/middlelayer/utils/errors.ts new file mode 100644 index 0000000..8a69eb4 --- /dev/null +++ b/middlelayer/utils/errors.ts @@ -0,0 +1,28 @@ +/** + * Custom Error-Klassen für den Middlelayer + */ +export class DataServiceError extends Error { + constructor( + message: string, + public readonly code: string, + public readonly originalError?: unknown + ) { + super(message); + this.name = 'DataServiceError'; + } +} + +export class AdapterError extends DataServiceError { + constructor(message: string, originalError?: unknown) { + super(message, 'ADAPTER_ERROR', originalError); + this.name = 'AdapterError'; + } +} + +export class NotFoundError extends DataServiceError { + constructor(resource: string, identifier: string) { + super(`${resource} mit ID '${identifier}' nicht gefunden`, 'NOT_FOUND'); + this.name = 'NotFoundError'; + } +} + diff --git a/middlelayer/utils/redisCache.ts b/middlelayer/utils/redisCache.ts new file mode 100644 index 0000000..ca188db --- /dev/null +++ b/middlelayer/utils/redisCache.ts @@ -0,0 +1,175 @@ +import Redis from "ioredis"; +import { cacheHits, cacheMisses } from "../monitoring/metrics.js"; +import { logCacheHit, logCacheMiss } from "../monitoring/logger.js"; +import { logger } from "../monitoring/logger.js"; + +/** + * Redis-basierter Cache mit Fallback auf In-Memory + */ +export class RedisCache { + private redis: Redis | null = null; + private memoryCache: Map = new Map(); + private defaultTTL: number; + private cacheType: string; + private useRedis: boolean; + + constructor(defaultTTL: number, cacheType: string) { + this.defaultTTL = defaultTTL; + this.cacheType = cacheType; + this.useRedis = process.env.REDIS_ENABLED === "true"; + + if (this.useRedis) { + this.initRedis(); + } else { + logger.info( + `Redis Cache deaktiviert für ${cacheType}, verwende In-Memory Cache` + ); + } + } + + private async initRedis() { + try { + this.redis = new Redis({ + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379"), + password: process.env.REDIS_PASSWORD, + retryStrategy: (times) => { + // Bei Fehler: Fallback auf In-Memory nach 3 Versuchen + if (times > 3) { + logger.warn( + "Redis-Verbindung fehlgeschlagen, verwende In-Memory Cache" + ); + this.useRedis = false; + return null; // Stoppe Retry + } + return Math.min(times * 50, 2000); + }, + maxRetriesPerRequest: 3, + }); + + this.redis.on("error", (error) => { + logger.error("Redis-Fehler", { error, cacheType: this.cacheType }); + // Fallback auf In-Memory bei Fehler + this.useRedis = false; + }); + + this.redis.on("connect", () => { + logger.info(`Redis verbunden für ${this.cacheType}`); + }); + + // Test-Verbindung + await this.redis.ping(); + logger.info(`Redis Cache aktiviert für ${this.cacheType}`); + } catch (error) { + logger.warn( + "Redis-Initialisierung fehlgeschlagen, verwende In-Memory Cache", + { + error, + cacheType: this.cacheType, + } + ); + this.useRedis = false; + } + } + + async set(key: string, data: T, ttl?: number): Promise { + const ttlMs = ttl || this.defaultTTL; + const serialized = JSON.stringify(data); + + if (this.useRedis && this.redis) { + try { + // Redis TTL in Sekunden + const ttlSeconds = Math.ceil(ttlMs / 1000); + await this.redis.setex(key, ttlSeconds, serialized); + return; + } catch (error) { + logger.warn("Redis set fehlgeschlagen, verwende In-Memory", { + error, + key, + }); + this.useRedis = false; + } + } + + // Fallback: In-Memory + const expiresAt = Date.now() + ttlMs; + this.memoryCache.set(key, { data, expiresAt }); + } + + async get(key: string): Promise { + if (this.useRedis && this.redis) { + try { + const value = await this.redis.get(key); + if (value) { + // Cache Hit + cacheHits.inc({ cache_type: this.cacheType }); + logCacheHit(key, this.cacheType); + return JSON.parse(value) as T; + } + // Cache Miss + cacheMisses.inc({ cache_type: this.cacheType }); + logCacheMiss(key, this.cacheType); + return null; + } catch (error) { + logger.warn("Redis get fehlgeschlagen, verwende In-Memory", { + error, + key, + }); + this.useRedis = false; + } + } + + // Fallback: In-Memory + const entry = this.memoryCache.get(key); + if (!entry) { + cacheMisses.inc({ cache_type: this.cacheType }); + logCacheMiss(key, this.cacheType); + return null; + } + + if (Date.now() > entry.expiresAt) { + this.memoryCache.delete(key); + cacheMisses.inc({ cache_type: this.cacheType }); + logCacheMiss(key, this.cacheType); + return null; + } + + cacheHits.inc({ cache_type: this.cacheType }); + logCacheHit(key, this.cacheType); + return entry.data; + } + + async clear(): Promise { + if (this.useRedis && this.redis) { + try { + // Lösche alle Keys mit unserem Prefix + const keys = await this.redis.keys(`${this.cacheType}:*`); + if (keys.length > 0) { + await this.redis.del(...keys); + } + } catch (error) { + logger.warn("Redis clear fehlgeschlagen", { error }); + } + } + this.memoryCache.clear(); + } + + async delete(key: string): Promise { + if (this.useRedis && this.redis) { + try { + await this.redis.del(key); + return; + } catch (error) { + logger.warn("Redis delete fehlgeschlagen", { error, key }); + } + } + this.memoryCache.delete(key); + } + + async disconnect(): Promise { + if (this.redis) { + await this.redis.quit(); + this.redis = null; + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ce48648 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9037 @@ +{ + "name": "sell", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "version": "0.0.1", + "dependencies": { + "@apollo/server": "^5.0.0", + "@apollo/server-plugin-response-cache": "^5.0.0", + "@astrojs/alpinejs": "^0.4.9", + "@astrojs/react": "^4.4.2", + "@tailwindcss/vite": "^4.1.18", + "@types/alpinejs": "^3.13.11", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "alpinejs": "^3.15.2", + "astro": "^5.16.5", + "bcryptjs": "^3.0.3", + "dataloader": "^2.2.3", + "graphql": "^16.12.0", + "graphql-query-complexity": "^1.1.0", + "ioredis": "^5.8.2", + "jsonwebtoken": "^9.0.3", + "marked": "^17.0.1", + "prom-client": "^15.1.3", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwindcss": "^4.1.18", + "winston": "^3.19.0" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^20.11.0", + "concurrently": "^8.2.2", + "tsx": "^4.7.0" + } + }, + "node_modules/@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "license": "MIT", + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/protobufjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "long": "^4.0.0" + }, + "bin": { + "apollo-pbjs": "bin/pbjs", + "apollo-pbts": "bin/pbts" + } + }, + "node_modules/@apollo/server": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.2.0.tgz", + "integrity": "sha512-OEAl5bwVitkvVkmZlgWksSnQ10FUr6q2qJMdkexs83lsvOGmd/y81X5LoETmKZux8UiQsy/A/xzP00b8hTHH/w==", + "license": "MIT", + "dependencies": { + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^2.0.0", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", + "async-retry": "^1.2.1", + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "finalhandler": "^2.1.0", + "loglevel": "^1.6.8", + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "graphql": "^16.11.0" + } + }, + "node_modules/@apollo/server-gateway-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "license": "MIT", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/server-plugin-response-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-plugin-response-cache/-/server-plugin-response-cache-5.0.0.tgz", + "integrity": "sha512-q78R0jUP642EjBvfzkAV5HtM0fcoA0/D2tBGQdXPShj6PvcV2UDhaw6Yw/N9rkXKJmFtXZiGyDyOssfWO1cTxw==", + "license": "MIT", + "dependencies": { + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@apollo/server": "^5.0.0", + "graphql": "^16.11.0" + } + }, + "node_modules/@apollo/server/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "license": "MIT", + "dependencies": { + "@apollo/protobufjs": "1.2.7" + } + }, + "node_modules/@apollo/utils.createhash": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", + "license": "MIT", + "dependencies": { + "@apollo/utils.isnodelike": "^3.0.0", + "sha.js": "^2.4.11" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.dropunuseddefinitions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", + "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.fetcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.isnodelike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", + "dependencies": { + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@apollo/utils.keyvaluecache/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@apollo/utils.printwithreducedwhitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", + "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.removealiases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", + "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.sortast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", + "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.stripsensitiveliterals": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", + "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.usagereporting": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", + "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "license": "MIT", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.dropunuseddefinitions": "^2.0.1", + "@apollo/utils.printwithreducedwhitespace": "^2.0.1", + "@apollo/utils.removealiases": "2.0.1", + "@apollo/utils.sortast": "^2.0.1", + "@apollo/utils.stripsensitiveliterals": "^2.0.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.withrequired": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@astrojs/alpinejs": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@astrojs/alpinejs/-/alpinejs-0.4.9.tgz", + "integrity": "sha512-fvKBAugn7yIngEKfdk6vL3ZlcVKtQvFXCZznG28OikGanKN5W+PkRPIdKaW/0gThRU2FyCemgzyHgyFjsH8dTA==", + "license": "MIT", + "peerDependencies": { + "@types/alpinejs": "^3.0.0", + "alpinejs": "^3.0.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", + "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", + "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.19.0", + "smol-toml": "^1.5.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/react": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.4.2.tgz", + "integrity": "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==", + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.7.0", + "ultrahtml": "^1.6.0", + "vite": "^6.4.1" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", + "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "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==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/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==", + "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==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-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==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-3.0.1.tgz", + "integrity": "sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==", + "license": "MIT", + "dependencies": { + "fontkit": "^2.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.6.tgz", + "integrity": "sha512-bTnP+4oom4nDjmkS3Ykbe+ljAp/RIiWP3R35COMmuucS24iQxGLa9Hn8VMkLIoaoPxgz6xk+dbC43jtkNsFoBw==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "10.0.30", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.30.tgz", + "integrity": "sha512-yPXU17uM/LR90t92yYQqn9mAJNOVZJc0nQtYeZyZeQZeQjwIGlTubvvoDL0fFVk+wZzs4YQOgds2NwSA4npodA==", + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.6", + "@graphql-tools/utils": "^10.11.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.11.0.tgz", + "integrity": "sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@ioredis/commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "license": "MIT" + }, + "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/@rollup/pluginutils/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/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.20.0.tgz", + "integrity": "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.20.0.tgz", + "integrity": "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.20.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/alpinejs": { + "version": "3.13.11", + "resolved": "https://registry.npmjs.org/@types/alpinejs/-/alpinejs-3.13.11.tgz", + "integrity": "sha512-3KhGkDixCPiLdL3Z/ok1GxHwLxEWqQOKJccgaQL01wc0EVM2tCTaqlC3NIedmxAXkVzt/V6VTM8qPgnOHKJ1MA==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "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/fontkit": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", + "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.26.tgz", + "integrity": "sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "license": "MIT" + }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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/alpinejs": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.2.tgz", + "integrity": "sha512-2kYF2aG+DTFkE6p0rHG5XmN4VEb6sO9b02aOdU4+i8QN6rL0DbRZQiypDE1gBcGO65yDcqMz5KKYUYgMUxgNkw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/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-align/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/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astro": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.16.5.tgz", + "integrity": "sha512-QeuM4xzTR0QuXFDNlGVW0BW7rcquKFIkylaPeM4ufii0/RRiPTYtwxDYVZ3KfiMRuuc+nbLD0214kMKTvz/yvQ==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.10", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^3.0.1", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.0.2", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.5.0", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.3.1", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.5.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.15.0", + "smol-toml": "^1.5.2", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.6.0", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.3", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.0", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", + "integrity": "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/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/concurrently/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/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "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/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", + "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/fontace": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz", + "integrity": "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==", + "license": "MIT", + "dependencies": { + "@types/fontkit": "^2.0.8", + "fontkit": "^2.0.4" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-query-complexity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-query-complexity/-/graphql-query-complexity-1.1.0.tgz", + "integrity": "sha512-6sfAX+9CgkcPeZ7UiuBwgTGA+M1FYgHrQOXvORhQGd6SiaXbNVkLDcJ9ZSvNgzyChIfH0uPFFOY3Jm4wFZ4qEA==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2" + }, + "peerDependencies": { + "graphql": "^15.0.0 || ^16.0.0" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", + "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.4.0", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "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==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "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==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "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/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.20.0.tgz", + "integrity": "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.20.0", + "@shikijs/engine-javascript": "3.20.0", + "@shikijs/engine-oniguruma": "3.20.0", + "@shikijs/langs": "3.20.0", + "@shikijs/themes": "3.20.0", + "@shikijs/types": "3.20.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", + "integrity": "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.6.0.tgz", + "integrity": "sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "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": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f996242 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "mock:server": "tsx middlelayer/index.ts", + "start": "concurrently \"npm run mock:server\" \"npm run dev\" --names \"GRAPHQL,ASTRO\" --prefix-colors \"cyan,yellow\"" + }, + "dependencies": { + "@apollo/server": "^5.0.0", + "@apollo/server-plugin-response-cache": "^5.0.0", + "@astrojs/alpinejs": "^0.4.9", + "@astrojs/react": "^4.4.2", + "@tailwindcss/vite": "^4.1.18", + "@types/alpinejs": "^3.13.11", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "alpinejs": "^3.15.2", + "astro": "^5.16.5", + "bcryptjs": "^3.0.3", + "dataloader": "^2.2.3", + "graphql": "^16.12.0", + "graphql-query-complexity": "^1.1.0", + "ioredis": "^5.8.2", + "jsonwebtoken": "^9.0.3", + "marked": "^17.0.1", + "prom-client": "^15.1.3", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwindcss": "^4.1.18", + "winston": "^3.19.0" + }, + "overrides": { + "graphql": "^16.12.0" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^20.11.0", + "concurrently": "^8.2.2", + "tsx": "^4.7.0" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..f157bd1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/assets/astro.svg b/src/assets/astro.svg new file mode 100644 index 0000000..8cf8fb0 --- /dev/null +++ b/src/assets/astro.svg @@ -0,0 +1 @@ + diff --git a/src/assets/background.svg b/src/assets/background.svg new file mode 100644 index 0000000..4b2be0a --- /dev/null +++ b/src/assets/background.svg @@ -0,0 +1 @@ + diff --git a/src/components/ContentCol.astro b/src/components/ContentCol.astro new file mode 100644 index 0000000..5c1a90c --- /dev/null +++ b/src/components/ContentCol.astro @@ -0,0 +1,37 @@ +--- +import type { ContentRow } from "@middlelayer-types/page"; + +interface Props { + justifyContent: ContentRow["justifyContent"]; + alignItems: ContentRow["alignItems"]; +} + +const { justifyContent, alignItems } = Astro.props; + +const justifyContentMap: Record = { + start: "flex-start", + end: "flex-end", + center: "center", + between: "space-between", + around: "space-around", + evenly: "space-evenly", +}; + +const alignItemsMap: Record = { + start: "flex-start", + end: "flex-end", + center: "center", + baseline: "baseline", + stretch: "stretch", +}; + +const justifyContentValue = justifyContentMap[justifyContent]; +const alignItemsValue = alignItemsMap[alignItems]; +--- + +
+ +
diff --git a/src/components/ContentRow.astro b/src/components/ContentRow.astro new file mode 100644 index 0000000..8b3f901 --- /dev/null +++ b/src/components/ContentRow.astro @@ -0,0 +1,45 @@ +--- +import type { ContentRow } from "@middlelayer-types/page"; +import ContentCol from "./ContentCol.astro"; +import C_html from "./cms/C_html.astro"; +import C_markdown from "./cms/C_markdown.astro"; +import C_iframe from "./cms/C_iframe.astro"; +import C_imageGallery from "./cms/C_imageGallery.astro"; +import C_image from "./cms/C_image.astro"; +import C_quote from "./cms/C_quote.astro"; +import C_youtubeVideo from "./cms/C_youtubeVideo.astro"; +import C_headline from "./cms/C_headline.astro"; + +interface Props { + row: ContentRow; +} + +const { row } = Astro.props; +--- + +
+ + { + row.content.map((item) => { + if (item.type === "html") { + return ; + } else if (item.type === "markdown") { + return ; + } else if (item.type === "iframe") { + return ; + } else if (item.type === "imageGallery") { + return ; + } else if (item.type === "image") { + return ; + } else if (item.type === "quote") { + return ; + } else if (item.type === "youtubeVideo") { + return ; + } else if (item.type === "headline") { + return ; + } + return null; + }) + } + +
diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..e9e5eb5 --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,26 @@ +--- +import LanguageSwitcher from "./LanguageSwitcher.tsx"; + +interface Props { + currentLocale: string; + pathWithoutLocale: string; +} + +const { currentLocale, pathWithoutLocale } = Astro.props; +--- + +
+
+
+
+ © {new Date().getFullYear()} Your Company. All rights reserved. +
+ +
+
+
+ diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..f88bb98 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +interface LanguageSwitcherProps { + currentLocale: string; + pathWithoutLocale: string; +} + +export default function LanguageSwitcher({ + currentLocale, + pathWithoutLocale, +}: LanguageSwitcherProps) { + const availableLocales = [ + { code: "de", name: "Deutsch", flag: "🇩🇪" }, + { code: "en", name: "English", flag: "🇬🇧" }, + ]; + + const switchLocale = (newLocale: string) => { + // Erstelle neue URL mit neuer Locale + const newPath = `/${newLocale}${pathWithoutLocale === "/" ? "" : pathWithoutLocale}`; + window.location.href = newPath; + }; + + return ( +
+ Language: +
+ {availableLocales.map((locale) => ( + + ))} +
+
+ ); +} + diff --git a/src/components/LoginModal.tsx b/src/components/LoginModal.tsx new file mode 100644 index 0000000..69c51a3 --- /dev/null +++ b/src/components/LoginModal.tsx @@ -0,0 +1,220 @@ +import { useState, useEffect } from "react"; +import { useI18n } from "../lib/i18n/useI18n.js"; + +interface User { + id: string; + email: string; + name: string; + role: string; + createdAt: string; +} + +interface LoginResponse { + login: { + user: User; + token: string; + }; +} + +interface GraphQLResponse { + data?: T; + errors?: Array<{ message: string }>; +} + +export default function LoginModal() { + const { t } = useI18n("auth"); + const [show, setShow] = useState(false); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + // Event-Listener für Modal-Öffnung + const handleOpen = () => setShow(true); + window.addEventListener("openLogin", handleOpen); + + // Body overflow beim Öffnen/Schließen steuern + if (show) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + + return () => { + window.removeEventListener("openLogin", handleOpen); + document.body.style.overflow = ""; + }; + }, [show]); + + const close = () => { + setShow(false); + setEmail(""); + setPassword(""); + setError(null); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + + try { + const GRAPHQL_URL = + window.APP_CONFIG?.GRAPHQL_URL || "http://localhost:4000"; + const response = await fetch(GRAPHQL_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: `mutation Login($email: String!, $password: String!) { + login(email: $email, password: $password) { + user { + id + email + name + role + createdAt + } + token + } + }`, + variables: { email, password }, + }), + }); + + const result: GraphQLResponse = await response.json(); + + if (result.errors) { + setError(result.errors[0].message || t("login.error")); + return; + } + + if (result.data?.login) { + localStorage.setItem("authToken", result.data.login.token); + localStorage.setItem( + "authUser", + JSON.stringify(result.data.login.user) + ); + window.dispatchEvent(new Event("authchange")); + close(); + window.location.reload(); + } + } catch (err) { + setError(t("login.error")); + } finally { + setLoading(false); + } + }; + + if (!show) return null; + + return ( +
{ + if (e.target === e.currentTarget) close(); + }} + onKeyDown={(e) => { + if (e.key === "Escape") close(); + }} + style={{ display: show ? "flex" : "none" }} + > +
e.stopPropagation()} + > +
+

{t("login.title")}

+ +
+ +
+
+ + setEmail(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="ihre@email.de" + /> +
+ +
+ + setPassword(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+

{error}

+
+ )} + + +
+ +
+

+ {t("login.noAccount")}{" "} + +

+
+
+
+ ); +} + diff --git a/src/components/PageContent.astro b/src/components/PageContent.astro new file mode 100644 index 0000000..b52b4b8 --- /dev/null +++ b/src/components/PageContent.astro @@ -0,0 +1,68 @@ +--- +import type { Page } from "@middlelayer-types/page"; +import ContentRow from "./ContentRow.astro"; + +interface Props { + page: Page; + locale: string; + showFallbackLink?: boolean; +} + +const { page, locale, showFallbackLink = false } = Astro.props; +--- + +
+ { + page.topFullwidthBanner && ( +
+
+ {page.topFullwidthBanner.imageUrl && ( + {page.topFullwidthBanner.headline} + )} +

+ {page.topFullwidthBanner.headline} +

+

+ {page.topFullwidthBanner.subheadline} +

+

+ {page.topFullwidthBanner.text} +

+
+
+ ) + } +
+

{page.headline}

+

{page.subheadline}

+
+ + {/* Content Rows */} + {page.row1 && } + {page.row2 && } + {page.row3 && } + + {/* Fallback: Link zur Homepage, wenn keine Content Rows vorhanden */} + { + showFallbackLink && !page.row1 && !page.row2 && !page.row3 && ( +
+ + {locale === "en" ? "Go to Homepage" : "Zur Startseite"} + +
+ ) + } +
diff --git a/src/components/Product.astro b/src/components/Product.astro new file mode 100644 index 0000000..1f30305 --- /dev/null +++ b/src/components/Product.astro @@ -0,0 +1,96 @@ +--- +import type { Product } from "../lib/types/product.js"; + +interface Props { + product: Product; +} + +const { product } = Astro.props; +--- + +
+ + { + product.promotion && ( +
+ {product.promotion.text} +
+ ) + } + + + { + product.imageUrl && ( + {product.name} + ) + } + +
+ +

{product.name}

+ + + { + product.category && ( + + {product.category} + + ) + } + + + { + product.description && ( +

+ {product.description} +

+ ) + } + + +
+
+
+ + + {product.price.toFixed(2)} + {product.currency} + + + { + product.originalPrice && ( + + {product.originalPrice.toFixed(2)} {product.currency} + + ) + } +
+
+ + + {product.inStock ? "✓ Verfügbar" : "✗ Ausverkauft"} + +
+
+
diff --git a/src/components/RegisterModal.tsx b/src/components/RegisterModal.tsx new file mode 100644 index 0000000..b8b8ebf --- /dev/null +++ b/src/components/RegisterModal.tsx @@ -0,0 +1,278 @@ +import { useState, useEffect } from "react"; +import { useI18n } from "../lib/i18n/useI18n"; + +interface User { + id: string; + email: string; + name: string; + role: string; + createdAt: string; +} + +interface RegisterResponse { + register: { + user: User; + token: string; + }; +} + +interface GraphQLResponse { + data?: T; + errors?: Array<{ message: string }>; +} + +export default function RegisterModal() { + const { t } = useI18n("auth"); + const [show, setShow] = useState(false); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + // Event-Listener für Modal-Öffnung + const handleOpen = () => setShow(true); + window.addEventListener("openRegister", handleOpen); + + // Body overflow beim Öffnen/Schließen steuern + if (show) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + + return () => { + window.removeEventListener("openRegister", handleOpen); + document.body.style.overflow = ""; + }; + }, [show]); + + const close = () => { + setShow(false); + setName(""); + setEmail(""); + setPassword(""); + setConfirmPassword(""); + setError(null); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (password !== confirmPassword) { + setError(t("register.passwordMismatch")); + return; + } + + if (password.length < 6) { + setError(t("register.passwordTooShort")); + return; + } + + setLoading(true); + setError(null); + + try { + const GRAPHQL_URL = + window.APP_CONFIG?.GRAPHQL_URL || "http://localhost:4000"; + const response = await fetch(GRAPHQL_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: `mutation Register($email: String!, $password: String!, $name: String!) { + register(email: $email, password: $password, name: $name) { + user { + id + email + name + role + createdAt + } + token + } + }`, + variables: { + email, + password, + name, + }, + }), + }); + + const result: GraphQLResponse = await response.json(); + + if (result.errors) { + setError(result.errors[0].message || t("register.error")); + return; + } + + if (result.data?.register) { + localStorage.setItem("authToken", result.data.register.token); + localStorage.setItem( + "authUser", + JSON.stringify(result.data.register.user) + ); + window.dispatchEvent(new Event("authchange")); + close(); + window.location.reload(); + } + } catch (err) { + setError(t("register.error")); + } finally { + setLoading(false); + } + }; + + if (!show) return null; + + return ( +
{ + if (e.target === e.currentTarget) close(); + }} + onKeyDown={(e) => { + if (e.key === "Escape") close(); + }} + style={{ display: show ? "flex" : "none" }} + > +
e.stopPropagation()} + > +
+

+ {t("register.title")} +

+ +
+ +
+
+ + setName(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Max Mustermann" + /> +
+ +
+ + setEmail(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="ihre@email.de" + /> +
+ +
+ + setPassword(e.target.value)} + required + minLength={6} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + required + minLength={6} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="••••••••" + /> +
+ + {error && ( +
+

{error}

+
+ )} + + +
+ +
+

+ {t("register.hasAccount")}{" "} + +

+
+
+
+ ); +} diff --git a/src/components/Welcome.astro b/src/components/Welcome.astro new file mode 100644 index 0000000..52e0333 --- /dev/null +++ b/src/components/Welcome.astro @@ -0,0 +1,210 @@ +--- +import astroLogo from '../assets/astro.svg'; +import background from '../assets/background.svg'; +--- + + + + diff --git a/src/components/cms/C_headline.astro b/src/components/cms/C_headline.astro new file mode 100644 index 0000000..c965cbc --- /dev/null +++ b/src/components/cms/C_headline.astro @@ -0,0 +1,23 @@ +--- +import type { c_headline } from "@middlelayer-types/c_headline"; +import { getLayoutClasses } from "../../lib/utils/layoutClasses.js"; + +interface Props { + item: c_headline; +} + +const { item } = Astro.props; +const layoutClasses = getLayoutClasses(item.layout); +const alignClass = item.align + ? item.align === "left" + ? "text-left" + : item.align === "center" + ? "text-center" + : "text-right" + : ""; +const Tag = item.tag; +--- + +
+ {item.text} +
diff --git a/src/components/cms/C_html.astro b/src/components/cms/C_html.astro new file mode 100644 index 0000000..8aa3ffb --- /dev/null +++ b/src/components/cms/C_html.astro @@ -0,0 +1,13 @@ +--- +import type { c_html } from "@middlelayer-types/c_html"; +import { getLayoutClasses } from "../../lib/utils/layoutClasses.js"; + +interface Props { + item: c_html; +} + +const { item } = Astro.props; +const layoutClasses = getLayoutClasses(item.layout); +--- + +
diff --git a/src/components/cms/C_iframe.astro b/src/components/cms/C_iframe.astro new file mode 100644 index 0000000..4701d2a --- /dev/null +++ b/src/components/cms/C_iframe.astro @@ -0,0 +1,32 @@ +--- +import type { c_iframe } from "@middlelayer-types/c_iframe"; +import { getLayoutClasses } from "../../lib/utils/layoutClasses.js"; + +interface Props { + item: c_iframe; +} + +const { item } = Astro.props; +const layoutClasses = getLayoutClasses(item.layout); +--- + +
+ {item.overlayImageUrl && ( + {item.name} + )} +
+
+ +
+ { + item.description && ( +

{item.description}

+ ) + } +
diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..85c2b80 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,9 @@ +/// + +declare namespace App { + interface Locals { + locale?: string; + pathWithoutLocale?: string; + } +} + diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro new file mode 100644 index 0000000..c1e7959 --- /dev/null +++ b/src/layouts/Layout.astro @@ -0,0 +1,218 @@ +--- +import "../styles/global.css"; +import { getPageSeo, getNavigation } from "../lib/graphql/cmsQueries.js"; +import LoginModal from "../components/LoginModal.tsx"; +import RegisterModal from "../components/RegisterModal.tsx"; +import Footer from "../components/Footer.astro"; + +interface Props { + title?: string; + locale?: string; +} + +const { title, locale } = Astro.props; +// Fallback: Hole Locale aus context.locals (von Middleware gesetzt) oder aus URL +const urlLocale = Astro.url.pathname.match(/^\/(de|en)(\/|$)/)?.[1]; +const currentLocale = locale || Astro.locals.locale || urlLocale || "de"; +// Hole pathWithoutLocale aus context.locals (von Middleware gesetzt) +const pathWithoutLocale = + Astro.locals.pathWithoutLocale || + Astro.url.pathname.replace(/^\/(de|en)/, "") || + "/"; + +let seo; +let navigation; +try { + [seo, navigation] = await Promise.all([ + getPageSeo(currentLocale), + getNavigation(currentLocale), + ]); +} catch (error) { + // Verbindungsfehler weniger störend behandeln + const isConnectionError = + error instanceof Error && + (error.name === "ConnectionError" || + error.message.includes("GraphQL-Server nicht erreichbar") || + error.message.includes("ECONNREFUSED")); + + if (isConnectionError) { + // In Development: Warnung ausgeben + if (import.meta.env.DEV) { + console.warn("GraphQL-Server nicht erreichbar. Verwende Fallback-Daten."); + console.warn("Starte den Server mit: npm run mock:server"); + } + } else { + console.error("Fehler beim Laden der Daten:", error); + } + + // Fallback-Werte + seo = { + title: title || "Astro Basics", + description: "", + metaRobotsIndex: "index", + metaRobotsFollow: "follow", + }; + navigation = { + name: "Navigation", + internal: "main-nav", + links: [], + }; +} + +// Verwende den übergebenen Titel oder den SEO-Titel +const pageTitle = title || seo.title; +--- + + + + + + + + + {pageTitle} + + + + + + +