From 4bdb5dea859433278fb0eb0fd2523e6ecd38054e Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:59:46 +0100 Subject: [PATCH] feat(manadeck): implement AI deck generation with Google Gemini MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AiService using Google Gemini Flash 2.0 for card generation - Support all card types: text, flashcard, quiz, mixed - Implement full generateDeckWithAI endpoint with: - Credit validation and consumption - Structured JSON output with response schema - Deck and card creation in PostgreSQL - Comprehensive error handling - Add @google/genai dependency - Update .env.example with GOOGLE_GENAI_API_KEY - Support difficulty levels (beginner/intermediate/advanced) - Support multiple languages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- manadeck/backend/.env.example | 13 +- manadeck/backend/package.json | 3 +- manadeck/backend/src/app.module.ts | 3 + .../backend/src/config/validation.schema.ts | 3 + .../backend/src/controllers/api.controller.ts | 161 ++++++++- manadeck/backend/src/services/ai.service.ts | 305 +++++++++++++++++ pnpm-lock.yaml | 313 ++++++++++++++---- 7 files changed, 717 insertions(+), 84 deletions(-) create mode 100644 manadeck/backend/src/services/ai.service.ts diff --git a/manadeck/backend/.env.example b/manadeck/backend/.env.example index 080e9e861..31c079185 100644 --- a/manadeck/backend/.env.example +++ b/manadeck/backend/.env.example @@ -5,16 +5,17 @@ PORT=8080 # Mana Core Configuration MANA_SERVICE_URL=https://mana-core-middleware-111768794939.europe-west3.run.app APP_ID=your-app-id-from-mana -SERVICE_KEY=your-service-key-from-mana-core # REQUIRED for credit operations +MANA_SUPABASE_SECRET_KEY=your-service-key-from-mana-core # REQUIRED for credit operations SIGNUP_REDIRECT_URL=https://manadeck.com/welcome -# Supabase Configuration (Your app's database) -SUPABASE_URL=https://your-project.supabase.co -SUPABASE_ANON_KEY=your-anon-key -SUPABASE_SERVICE_KEY=your-service-key +# PostgreSQL Database (Drizzle ORM) +DATABASE_URL=postgresql://postgres:postgres@localhost:5433/manadeck + +# AI Services (Google Gemini) +GOOGLE_GENAI_API_KEY=your-google-genai-api-key # Get from https://aistudio.google.com/apikey # JWT Configuration JWT_SECRET=your-jwt-secret # CORS Configuration -FRONTEND_URL=http://localhost:8081 \ No newline at end of file +FRONTEND_URL=http://localhost:8081 diff --git a/manadeck/backend/package.json b/manadeck/backend/package.json index 1986dd57d..40dd96eea 100644 --- a/manadeck/backend/package.json +++ b/manadeck/backend/package.json @@ -20,8 +20,9 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@manacore/manadeck-database": "workspace:*", + "@google/genai": "^1.14.0", "@mana-core/nestjs-integration": "git+https://github.com/Memo-2023/mana-core-nestjs-package.git", + "@manacore/manadeck-database": "workspace:*", "@nestjs/axios": "^4.0.1", "@nestjs/common": "^11.0.1", "@nestjs/config": "^4.0.2", diff --git a/manadeck/backend/src/app.module.ts b/manadeck/backend/src/app.module.ts index 714392002..89b42686d 100644 --- a/manadeck/backend/src/app.module.ts +++ b/manadeck/backend/src/app.module.ts @@ -10,6 +10,7 @@ import { ApiController } from './controllers/api.controller'; import { PublicController } from './controllers/public.controller'; import { HealthController } from './controllers/health.controller'; import { validationSchema } from './config/validation.schema'; +import { AiService } from './services/ai.service'; import { DatabaseModule, DeckRepository, @@ -64,6 +65,8 @@ import { ], providers: [ AppService, + // AI Service + AiService, // Database repositories DeckRepository, CardRepository, diff --git a/manadeck/backend/src/config/validation.schema.ts b/manadeck/backend/src/config/validation.schema.ts index 94bcb1c65..9e4644841 100644 --- a/manadeck/backend/src/config/validation.schema.ts +++ b/manadeck/backend/src/config/validation.schema.ts @@ -20,4 +20,7 @@ export const validationSchema = Joi.object({ // CORS FRONTEND_URL: Joi.string().uri().optional(), + + // AI Services + GOOGLE_GENAI_API_KEY: Joi.string().optional(), }); \ No newline at end of file diff --git a/manadeck/backend/src/controllers/api.controller.ts b/manadeck/backend/src/controllers/api.controller.ts index c2d452965..3f7fb1c45 100644 --- a/manadeck/backend/src/controllers/api.controller.ts +++ b/manadeck/backend/src/controllers/api.controller.ts @@ -1,9 +1,10 @@ -import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Logger, BadRequestException, NotImplementedException } from '@nestjs/common'; +import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Logger, BadRequestException, ServiceUnavailableException } from '@nestjs/common'; import { AuthGuard } from '@mana-core/nestjs-integration/guards'; import { CurrentUser } from '@mana-core/nestjs-integration/decorators'; import { CreditClientService } from '@mana-core/nestjs-integration'; import { CreditOperationType, getCreditCost, getOperationDescription } from '../config/credit-operations'; import { DeckRepository, CardRepository, UserStatsRepository } from '../database'; +import { AiService, CardType } from '../services/ai.service'; @Controller('api') @UseGuards(AuthGuard) @@ -15,6 +16,7 @@ export class ApiController { private readonly deckRepository: DeckRepository, private readonly cardRepository: CardRepository, private readonly userStatsRepository: UserStatsRepository, + private readonly aiService: AiService, ) {} @Get('profile') @@ -151,12 +153,157 @@ export class ApiController { async generateDeckWithAI(@CurrentUser() user: any, @Body() requestData: any) { this.logger.log(`AI deck generation requested by user: ${user.sub}`); - // TODO: Implement AI deck generation with a self-hosted solution - // This endpoint previously used Supabase Edge Functions which are no longer available - throw new NotImplementedException({ - error: 'not_implemented', - message: 'AI deck generation is currently being migrated to a new infrastructure. Please check back later.', - }); + // Check if AI service is available + if (!this.aiService.isAvailable()) { + throw new ServiceUnavailableException({ + error: 'ai_service_unavailable', + message: 'AI service is not configured. Please contact support.', + }); + } + + // Validate request + const { prompt, deckTitle, deckDescription, cardCount = 10, cardTypes, difficulty, tags, language } = requestData; + + if (!prompt || !deckTitle) { + throw new BadRequestException({ + error: 'validation_failed', + message: 'prompt and deckTitle are required', + }); + } + + if (cardCount < 1 || cardCount > 50) { + throw new BadRequestException({ + error: 'validation_failed', + message: 'cardCount must be between 1 and 50', + }); + } + + // Validate card types + const validCardTypes: CardType[] = ['text', 'flashcard', 'quiz', 'mixed']; + const requestedTypes: CardType[] = cardTypes || ['flashcard', 'quiz']; + const invalidTypes = requestedTypes.filter(t => !validCardTypes.includes(t)); + if (invalidTypes.length > 0) { + throw new BadRequestException({ + error: 'validation_failed', + message: `Invalid card types: ${invalidTypes.join(', ')}. Valid types: ${validCardTypes.join(', ')}`, + }); + } + + const operationType = CreditOperationType.AI_DECK_GENERATION; + const creditCost = getCreditCost(operationType); + + try { + // 1. Pre-flight credit validation + const validation = await this.creditClient.validateCredits( + user.sub, + operationType, + creditCost, + ); + + if (!validation.hasCredits) { + this.logger.warn( + `User ${user.sub} has insufficient credits for AI deck generation. Required: ${creditCost}, Available: ${validation.availableCredits}`, + ); + + throw new BadRequestException({ + error: 'insufficient_credits', + message: `Insufficient mana. Required: ${creditCost}, Available: ${validation.availableCredits}`, + requiredCredits: creditCost, + availableCredits: validation.availableCredits, + operation: getOperationDescription(operationType), + }); + } + + // 2. Generate cards with AI + this.logger.log(`Generating ${cardCount} cards with AI for user ${user.sub}...`); + const aiResult = await this.aiService.generateDeck({ + prompt, + deckTitle, + deckDescription, + cardCount, + cardTypes: requestedTypes, + difficulty: difficulty || 'intermediate', + language: language || 'en', + }); + + if (!aiResult.success || aiResult.cards.length === 0) { + throw new BadRequestException({ + error: 'ai_generation_failed', + message: aiResult.error || 'Failed to generate cards with AI', + }); + } + + // 3. Create deck in database + const newDeck = await this.deckRepository.create({ + userId: user.sub, + title: deckTitle, + description: deckDescription, + isPublic: false, + settings: { aiGenerated: true, difficulty }, + tags: tags || [], + metadata: { + aiModel: aiResult.metadata.model, + generationTime: aiResult.metadata.generationTime, + prompt, + }, + }); + + // 4. Create cards in database + const cardsToCreate = aiResult.cards.map((card, index) => ({ + deckId: newDeck.id, + title: card.title || `Card ${index + 1}`, + content: card.content, + cardType: card.cardType, + position: index, + aiModel: aiResult.metadata.model, + aiPrompt: prompt, + })); + + await this.cardRepository.createMany(cardsToCreate); + + // 5. Consume credits + await this.creditClient.consumeCredits( + user.sub, + operationType, + creditCost, + `Generated AI deck: ${deckTitle}`, + { + deckId: newDeck.id, + deckTitle, + cardCount: aiResult.cards.length, + prompt, + }, + ); + + this.logger.log( + `AI deck generated successfully for user ${user.sub}. ` + + `${aiResult.cards.length} cards created in ${aiResult.metadata.generationTime}ms. ` + + `${creditCost} credits consumed.` + ); + + return { + success: true, + userId: user.sub, + deck: newDeck, + cards: aiResult.cards, + cardCount: aiResult.cards.length, + creditsUsed: creditCost, + metadata: aiResult.metadata, + message: 'Deck generated successfully with AI', + }; + } catch (error) { + // If it's already a known exception, rethrow it + if (error instanceof BadRequestException || error instanceof ServiceUnavailableException) { + throw error; + } + + // Log other errors + this.logger.error(`Error generating AI deck for user ${user.sub}:`, error); + throw new BadRequestException({ + error: 'deck_generation_failed', + message: error.message || 'Failed to generate deck with AI', + }); + } } @Put('decks/:id') diff --git a/manadeck/backend/src/services/ai.service.ts b/manadeck/backend/src/services/ai.service.ts new file mode 100644 index 000000000..482fcf847 --- /dev/null +++ b/manadeck/backend/src/services/ai.service.ts @@ -0,0 +1,305 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { GoogleGenAI, Type } from '@google/genai'; + +export type CardType = 'text' | 'flashcard' | 'quiz' | 'mixed'; + +export interface TextContent { + text: string; +} + +export interface FlashcardContent { + front: string; + back: string; + hint?: string; +} + +export interface QuizContent { + question: string; + options: string[]; + correctAnswer: number; + explanation?: string; +} + +export interface GeneratedCard { + cardType: CardType; + title?: string; + content: TextContent | FlashcardContent | QuizContent; +} + +export interface DeckGenerationRequest { + prompt: string; + deckTitle: string; + deckDescription?: string; + cardCount?: number; + cardTypes?: CardType[]; + difficulty?: 'beginner' | 'intermediate' | 'advanced'; + language?: string; +} + +export interface DeckGenerationResult { + success: boolean; + cards: GeneratedCard[]; + metadata: { + model: string; + tokensUsed?: number; + generationTime: number; + }; + error?: string; +} + +@Injectable() +export class AiService { + private readonly logger = new Logger(AiService.name); + private readonly ai: GoogleGenAI | null; + private readonly model = 'gemini-2.0-flash'; + + constructor(private readonly configService: ConfigService) { + const apiKey = this.configService.get('GOOGLE_GENAI_API_KEY'); + + if (apiKey) { + this.ai = new GoogleGenAI({ apiKey }); + this.logger.log('Google Gemini AI initialized successfully'); + } else { + this.ai = null; + this.logger.warn('Google Gemini API key not configured - AI features disabled'); + } + } + + isAvailable(): boolean { + return this.ai !== null; + } + + async generateDeck(request: DeckGenerationRequest): Promise { + const startTime = Date.now(); + + if (!this.ai) { + return { + success: false, + cards: [], + metadata: { model: this.model, generationTime: 0 }, + error: 'AI service not configured. Please set GOOGLE_GENAI_API_KEY.', + }; + } + + const { + prompt, + deckTitle, + deckDescription, + cardCount = 10, + cardTypes = ['flashcard', 'quiz'], + difficulty = 'intermediate', + language = 'en', + } = request; + + try { + const systemPrompt = this.buildSystemPrompt(cardTypes, difficulty, language); + const userPrompt = this.buildUserPrompt(prompt, deckTitle, deckDescription, cardCount, cardTypes); + + const response = await this.ai.models.generateContent({ + model: this.model, + contents: userPrompt, + config: { + systemInstruction: systemPrompt, + responseMimeType: 'application/json', + responseSchema: this.buildResponseSchema(cardTypes), + }, + }); + + const generationTime = Date.now() - startTime; + const responseText = response.text?.trim(); + + if (!responseText) { + return { + success: false, + cards: [], + metadata: { model: this.model, generationTime }, + error: 'Empty response from AI', + }; + } + + const parsed = JSON.parse(responseText); + const cards: GeneratedCard[] = parsed.cards || []; + + this.logger.log(`Generated ${cards.length} cards in ${generationTime}ms`); + + return { + success: true, + cards, + metadata: { + model: this.model, + tokensUsed: response.usageMetadata?.totalTokenCount, + generationTime, + }, + }; + } catch (error) { + const generationTime = Date.now() - startTime; + this.logger.error('AI deck generation failed:', error); + + return { + success: false, + cards: [], + metadata: { model: this.model, generationTime }, + error: error instanceof Error ? error.message : 'Unknown error occurred', + }; + } + } + + private buildSystemPrompt(cardTypes: CardType[], difficulty: string, language: string): string { + const cardTypeDescriptions = { + text: 'Text cards contain informational content or explanations.', + flashcard: 'Flashcards have a front (question/term) and back (answer/definition), optionally with a hint.', + quiz: 'Quiz cards have a question, 4 options (A-D), correct answer index (0-3), and an explanation.', + mixed: 'Mixed cards combine multiple content types.', + }; + + const enabledTypes = cardTypes.map(t => `- ${t}: ${cardTypeDescriptions[t]}`).join('\n'); + + return `You are an expert educational content creator specializing in flashcards and study materials. + +Your task is to generate high-quality learning cards for a deck based on the user's topic. + +CARD TYPES YOU CAN CREATE: +${enabledTypes} + +DIFFICULTY LEVEL: ${difficulty} +- beginner: Simple concepts, basic vocabulary, straightforward questions +- intermediate: More complex topics, requires some prior knowledge +- advanced: Deep understanding, nuanced questions, expert-level content + +LANGUAGE: ${language === 'de' ? 'German' : language === 'en' ? 'English' : language} +Generate all content in this language. + +QUALITY GUIDELINES: +1. Make content educational and accurate +2. Vary question styles to keep learning engaging +3. For flashcards: front should be concise, back should be complete but not verbose +4. For quiz: all 4 options should be plausible, avoid obviously wrong answers +5. Include helpful hints for difficult flashcards +6. Add explanations for quiz questions to reinforce learning +7. Progress from easier to harder cards when possible`; + } + + private buildUserPrompt( + prompt: string, + deckTitle: string, + deckDescription?: string, + cardCount: number = 10, + cardTypes: CardType[] = ['flashcard', 'quiz'], + ): string { + const typeDistribution = this.suggestTypeDistribution(cardCount, cardTypes); + + return `Create a deck of ${cardCount} learning cards about: + +DECK TITLE: ${deckTitle} +${deckDescription ? `DESCRIPTION: ${deckDescription}` : ''} + +USER'S REQUEST: +${prompt} + +CARD DISTRIBUTION: +${typeDistribution} + +Generate exactly ${cardCount} cards that cover the topic comprehensively. +Ensure variety in the questions and good coverage of the subject matter.`; + } + + private suggestTypeDistribution(cardCount: number, cardTypes: CardType[]): string { + if (cardTypes.length === 1) { + return `- All ${cardCount} cards should be ${cardTypes[0]} type`; + } + + const hasFlashcard = cardTypes.includes('flashcard'); + const hasQuiz = cardTypes.includes('quiz'); + const hasText = cardTypes.includes('text'); + + if (hasFlashcard && hasQuiz && !hasText) { + const flashcardCount = Math.ceil(cardCount * 0.6); + const quizCount = cardCount - flashcardCount; + return `- ${flashcardCount} flashcards for core concepts\n- ${quizCount} quiz cards to test understanding`; + } + + if (hasFlashcard && hasQuiz && hasText) { + const textCount = Math.ceil(cardCount * 0.2); + const flashcardCount = Math.ceil((cardCount - textCount) * 0.6); + const quizCount = cardCount - textCount - flashcardCount; + return `- ${textCount} text cards for introductions/explanations\n- ${flashcardCount} flashcards for key terms\n- ${quizCount} quiz cards for testing`; + } + + return `- Mix of ${cardTypes.join(', ')} cards as appropriate for the content`; + } + + private buildResponseSchema(cardTypes: CardType[]): any { + const cardSchemas: any[] = []; + + if (cardTypes.includes('flashcard')) { + cardSchemas.push({ + type: Type.OBJECT, + properties: { + cardType: { type: Type.STRING, enum: ['flashcard'] }, + title: { type: Type.STRING }, + content: { + type: Type.OBJECT, + properties: { + front: { type: Type.STRING }, + back: { type: Type.STRING }, + hint: { type: Type.STRING }, + }, + required: ['front', 'back'], + }, + }, + required: ['cardType', 'content'], + }); + } + + if (cardTypes.includes('quiz')) { + cardSchemas.push({ + type: Type.OBJECT, + properties: { + cardType: { type: Type.STRING, enum: ['quiz'] }, + title: { type: Type.STRING }, + content: { + type: Type.OBJECT, + properties: { + question: { type: Type.STRING }, + options: { type: Type.ARRAY, items: { type: Type.STRING } }, + correctAnswer: { type: Type.NUMBER }, + explanation: { type: Type.STRING }, + }, + required: ['question', 'options', 'correctAnswer'], + }, + }, + required: ['cardType', 'content'], + }); + } + + if (cardTypes.includes('text')) { + cardSchemas.push({ + type: Type.OBJECT, + properties: { + cardType: { type: Type.STRING, enum: ['text'] }, + title: { type: Type.STRING }, + content: { + type: Type.OBJECT, + properties: { + text: { type: Type.STRING }, + }, + required: ['text'], + }, + }, + required: ['cardType', 'content'], + }); + } + + return { + type: Type.OBJECT, + properties: { + cards: { + type: Type.ARRAY, + items: cardSchemas.length === 1 ? cardSchemas[0] : { anyOf: cardSchemas }, + }, + }, + required: ['cards'], + }; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbe9a3d2f..3b0a13262 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,7 +312,7 @@ importers: version: 17.0.7(expo@54.0.25)(react@19.1.0) expo-router: specifier: ~6.0.14 - version: 6.0.15(jqlydxfw6sdg7rjjcbafgjr6wy) + version: 6.0.15(wy3aqjqih33pc4tbjsiq2nsp7q) expo-secure-store: specifier: ~15.0.7 version: 15.0.7(expo@54.0.25) @@ -415,7 +415,7 @@ importers: version: 7.28.5 '@testing-library/react-native': specifier: ^13.3.3 - version: 13.3.3(jest@29.5.0)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + version: 13.3.3(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) '@types/jest': specifier: ^29.5.12 version: 29.5.14 @@ -433,10 +433,10 @@ importers: version: 10.0.0 jest: specifier: ^29.2.1 - version: 29.5.0(@types/node@18.15.11)(ts-node@10.9.2(@types/node@18.15.11)(typescript@5.9.3)) + version: 29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) jest-expo: specifier: ~54.0.13 - version: 54.0.13(@babel/core@7.28.5)(expo@54.0.25)(jest@29.5.0)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)(webpack@5.100.2) + version: 54.0.13(@babel/core@7.28.5)(expo@54.0.25)(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)(webpack@5.100.2) patch-package: specifier: ^8.0.0 version: 8.0.1 @@ -557,6 +557,9 @@ importers: '@iconify-json/tabler': specifier: ^1.2.19 version: 1.2.23 + '@manacore/shared-landing-ui': + specifier: workspace:* + version: link:../../../packages/shared-landing-ui astro: specifier: ^5.16.0 version: 5.16.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) @@ -1008,9 +1011,6 @@ importers: '@manacore/shared-subscription-ui': specifier: workspace:* version: link:../../../packages/shared-subscription-ui - '@manacore/shared-supabase': - specifier: workspace:* - version: link:../../../packages/shared-supabase '@manacore/shared-tailwind': specifier: workspace:* version: link:../../../packages/shared-tailwind @@ -1029,9 +1029,6 @@ importers: '@manacore/shared-utils': specifier: workspace:* version: link:../../../packages/shared-utils - '@supabase/supabase-js': - specifier: ^2.81.1 - version: 2.81.1 svelte-i18n: specifier: ^4.0.1 version: 4.0.1(svelte@5.43.14) @@ -1075,6 +1072,9 @@ importers: manadeck/backend: dependencies: + '@google/genai': + specifier: ^1.14.0 + version: 1.30.0 '@mana-core/nestjs-integration': specifier: git+https://github.com/Memo-2023/mana-core-nestjs-package.git version: git+https://git@github.com:Memo-2023/mana-core-nestjs-package.git#eca58cc09d6e55d4f9d34e719fa0e0ed8d0101c7(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/config@4.0.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2))(@nestjs/core@11.1.9)(axios@1.13.2)(rxjs@7.8.2) @@ -1099,9 +1099,6 @@ importers: '@nestjs/terminus': specifier: ^11.0.0 version: 11.0.0(@grpc/grpc-js@1.14.1)(@grpc/proto-loader@0.8.0)(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@supabase/supabase-js': - specifier: ^2.81.1 - version: 2.81.1 axios: specifier: ^1.7.2 version: 1.13.2 @@ -1211,8 +1208,11 @@ importers: '@iconify-json/mdi': specifier: ^1.2.3 version: 1.2.3 + '@manacore/shared-landing-ui': + specifier: workspace:* + version: link:../../../packages/shared-landing-ui astro: - specifier: ^5.3.0 + specifier: ^5.16.0 version: 5.16.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) astro-icon: specifier: ^1.1.5 @@ -1726,7 +1726,7 @@ importers: specifier: ^0.9.0 version: 0.9.5(prettier-plugin-astro@0.14.1)(prettier@3.6.2)(typescript@5.9.3) astro: - specifier: ^5.3.0 + specifier: ^5.16.0 version: 5.16.0(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: ^5.0.0 @@ -1864,7 +1864,7 @@ importers: specifier: workspace:* version: link:../../packages/design-tokens astro: - specifier: ^5.2.0 + specifier: ^5.16.0 version: 5.16.0(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) astro-i18next: specifier: 1.0.0-beta.21 @@ -2075,6 +2075,30 @@ importers: picture/apps/web: dependencies: + '@manacore/shared-auth-ui': + specifier: workspace:* + version: link:../../../packages/shared-auth-ui + '@manacore/shared-branding': + specifier: workspace:* + version: link:../../../packages/shared-branding + '@manacore/shared-i18n': + specifier: workspace:* + version: link:../../../packages/shared-i18n + '@manacore/shared-subscription-types': + specifier: workspace:* + version: link:../../../packages/shared-subscription-types + '@manacore/shared-subscription-ui': + specifier: workspace:* + version: link:../../../packages/shared-subscription-ui + '@manacore/shared-tailwind': + specifier: workspace:* + version: link:../../../packages/shared-tailwind + '@manacore/shared-theme-ui': + specifier: workspace:* + version: link:../../../packages/shared-theme-ui + '@manacore/shared-ui': + specifier: workspace:* + version: link:../../../packages/shared-ui '@picture/design-tokens': specifier: workspace:* version: link:../../packages/design-tokens @@ -2090,6 +2114,9 @@ importers: posthog-js: specifier: ^1.273.1 version: 1.297.2 + svelte-i18n: + specifier: ^4.0.1 + version: 4.0.1(svelte@5.43.14) devDependencies: '@eslint/compat': specifier: ^1.4.0 @@ -17545,6 +17572,41 @@ snapshots: - supports-color - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 @@ -19543,7 +19605,7 @@ snapshots: tailwindcss: 4.1.17 vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - '@testing-library/react-native@13.3.3(jest@29.5.0)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + '@testing-library/react-native@13.3.3(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: jest-matcher-utils: 30.2.0 picocolors: 1.1.1 @@ -19553,7 +19615,7 @@ snapshots: react-test-renderer: 19.1.0(react@19.1.0) redent: 3.0.0 optionalDependencies: - jest: 29.5.0(@types/node@18.15.11)(ts-node@10.9.2(@types/node@18.15.11)(typescript@5.9.3)) + jest: 29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) '@testing-library/react-native@13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: @@ -19565,7 +19627,7 @@ snapshots: react-test-renderer: 19.1.0(react@19.1.0) redent: 3.0.0 optionalDependencies: - jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) optional: true '@testing-library/react-native@13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': @@ -19578,7 +19640,7 @@ snapshots: react-test-renderer: 19.1.0(react@19.1.0) redent: 3.0.0 optionalDependencies: - jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) optional: true '@tokenizer/inflate@0.2.7': @@ -22070,6 +22132,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-require@1.1.1: {} cross-fetch@3.2.0: @@ -24124,52 +24201,6 @@ snapshots: - '@types/react-dom' - supports-color - expo-router@6.0.15(jqlydxfw6sdg7rjjcbafgjr6wy): - dependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@expo/schema-utils': 0.1.7 - '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native-stack': 7.7.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) - expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-server: 1.0.4 - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 19.1.0 - react-fast-compare: 3.2.2 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.1.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.1.0) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@testing-library/react-native': 13.3.3(jest@29.5.0)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) - react-dom: 19.1.0(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - supports-color - expo-router@6.0.15(qjp3usx4acoq47dkosl6pmu254): dependencies: '@expo/metro-runtime': 6.1.2(expo@54.0.13)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) @@ -24262,6 +24293,52 @@ snapshots: - '@types/react-dom' - supports-color + expo-router@6.0.15(wy3aqjqih33pc4tbjsiq2nsp7q): + dependencies: + '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@expo/schema-utils': 0.1.7 + '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': 7.7.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) + expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-server: 1.0.4 + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.1.0 + react-fast-compare: 3.2.2 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.1.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.1.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - supports-color + expo-secure-store@15.0.7(expo@54.0.13): dependencies: expo: 54.0.13(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) @@ -25989,6 +26066,25 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) @@ -26008,6 +26104,26 @@ snapshots: - supports-color - ts-node + jest-cli@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)): + dependencies: + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-util: 30.2.0 + jest-validate: 30.2.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + optional: true + jest-config@29.7.0(@types/node@18.15.11)(ts-node@10.9.2(@types/node@18.15.11)(typescript@5.9.3)): dependencies: '@babel/core': 7.28.5 @@ -26070,6 +26186,37 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + dependencies: + '@babel/core': 7.28.5 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.5) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.19.1 + ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: '@babel/core': 7.28.5 @@ -26176,7 +26323,7 @@ snapshots: jest-util: 30.2.0 jest-validate: 30.2.0 - jest-expo@54.0.13(@babel/core@7.28.5)(expo@54.0.25)(jest@29.5.0)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)(webpack@5.100.2): + jest-expo@54.0.13(@babel/core@7.28.5)(expo@54.0.25)(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)(webpack@5.100.2): dependencies: '@expo/config': 12.0.10 '@expo/json-file': 10.0.7 @@ -26187,7 +26334,7 @@ snapshots: jest-environment-jsdom: 29.7.0 jest-snapshot: 29.7.0 jest-watch-select-projects: 2.0.0 - jest-watch-typeahead: 2.2.1(jest@29.5.0) + jest-watch-typeahead: 2.2.1(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) json5: 2.2.3 lodash: 4.17.21 react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) @@ -26548,11 +26695,11 @@ snapshots: chalk: 3.0.0 prompts: 2.4.2 - jest-watch-typeahead@2.2.1(jest@29.5.0): + jest-watch-typeahead@2.2.1(jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))): dependencies: ansi-escapes: 6.2.1 chalk: 4.1.2 - jest: 29.5.0(@types/node@18.15.11)(ts-node@10.9.2(@types/node@18.15.11)(typescript@5.9.3)) + jest: 29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -26614,6 +26761,18 @@ snapshots: - supports-color - ts-node + jest@29.5.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) @@ -26627,6 +26786,20 @@ snapshots: - supports-color - ts-node + jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)): + dependencies: + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/types': 30.2.0 + import-local: 3.2.0 + jest-cli: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + optional: true + jimp-compact@0.16.1: {} jiti@1.21.7: {}