diff --git a/docs/multi-step-integration-guide.md b/docs/multi-step-integration-guide.md new file mode 100644 index 0000000..84e271f --- /dev/null +++ b/docs/multi-step-integration-guide.md @@ -0,0 +1,491 @@ +# Multi-Step README Generation Pipeline - Integration Guide + +This guide provides complete instructions for integrating the multi-step README generation pipeline into an existing Next.js application, specifically for the ReadmeGenAI project. + +## ๐Ÿ“‹ Overview + +The new pipeline solves the token limit issues by: +- **Section-by-section generation**: Each section is generated individually within token limits +- **Retry logic**: Failed sections are automatically retried with simplified prompts +- **Smart dependency management**: Sections are generated in optimal order based on dependencies +- **Continuation support**: Truncated content can be automatically completed +- **Fallback mechanisms**: Critical sections always have fallback content + +## ๐Ÿš€ Quick Integration + +### 1. Install Dependencies + +```bash +npm install @google/generative-ai @octokit/rest +``` + +### 2. Replace Existing API Route + +Replace the content of `src/app/api/generate/route.ts`: + +```typescript +import { handleReadmeGeneration } from '@/lib/multi-step-readme-generator'; + +export async function POST(request: Request) { + return handleReadmeGeneration(request); +} +``` + +### 3. Environment Variables + +Ensure these environment variables are set: + +```env +GEMINI_API_KEY=your_gemini_api_key +GITHUB_TOKEN=your_github_token # Optional but recommended for higher rate limits +``` + +### 4. Update Frontend (Optional) + +Enhance the frontend to show generation progress: + +```typescript +// In your component +const [generationStats, setGenerationStats] = useState(null); + +const handleGenerate = async (githubUrl: string) => { + setIsLoading(true); + + try { + const response = await fetch('/api/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ githubUrl }), + }); + + const result = await response.json(); + + if (result.success) { + setReadme(result.readme); + setGenerationStats(result.stats); + } else { + setError(result.error); + } + } catch (error) { + setError('Generation failed'); + } finally { + setIsLoading(false); + } +}; +``` + +## ๐Ÿ”ง Advanced Configuration + +### Custom Configuration + +You can customize the generation behavior: + +```typescript +import { MultiStepReadmeGenerator } from '@/lib/multi-step-readme-generator'; + +const generator = new MultiStepReadmeGenerator( + process.env.GEMINI_API_KEY!, + process.env.GITHUB_TOKEN, + { + maxRetries: 5, // Increase retries for better reliability + maxTokensPerSection: 1000, // Allow longer sections + temperature: 0.5, // More conservative generation + concurrentSections: 2, // Reduce concurrency to avoid rate limits + enableContinuation: true, // Enable automatic continuation + } +); +``` + +### Custom Section Planning + +Define custom sections for specific project types: + +```typescript +import { SectionPlanner, ReadmeSection } from '@/lib/multi-step-readme-generator'; + +// Custom sections for a specific project type +const customSections: ReadmeSection[] = [ + { + id: 'header', + title: 'Project Header', + priority: 'critical', + order: 1, + estimatedTokens: 200, + dependencies: [], + }, + { + id: 'quick-start', + title: 'Quick Start', + priority: 'high', + order: 2, + estimatedTokens: 400, + dependencies: ['header'], + }, + // ... more sections +]; + +const result = await assembler.generateCompleteReadme( + metadata, + structure, + customSections +); +``` + +## ๐Ÿ“Š Monitoring and Analytics + +### Generation Stats + +The new pipeline provides detailed statistics: + +```typescript +interface GenerationStats { + sectionsGenerated: number; // How many sections were successfully generated + sectionsTotal: number; // Total sections planned + tokensUsed: number; // Total tokens consumed + timeElapsed: number; // Generation time in milliseconds +} +``` + +### Error Handling + +Comprehensive error information: + +```typescript +interface GenerationResult { + success: boolean; + readme?: string; + stats: GenerationStats; + errors: string[]; // Detailed error messages +} +``` + +### Logging Integration + +Add logging to track generation performance: + +```typescript +// In your API route +const result = await generator.generateReadme(githubUrl); + +// Log metrics +console.log(`README generated for ${githubUrl}:`, { + success: result.success, + sectionsGenerated: result.stats.sectionsGenerated, + timeElapsed: result.stats.timeElapsed, + tokensUsed: result.stats.tokensUsed, +}); + +// Log errors for debugging +if (result.errors.length > 0) { + console.error('Generation errors:', result.errors); +} +``` + +## ๐Ÿ”„ Migration from Existing Implementation + +### Step 1: Backup Current Implementation + +```bash +# Backup current generate route +cp src/app/api/generate/route.ts src/app/api/generate/route.ts.backup +``` + +### Step 2: Gradual Migration + +Implement a feature flag for gradual rollout: + +```typescript +// src/app/api/generate/route.ts +import { handleReadmeGeneration as newHandler } from '@/lib/multi-step-readme-generator'; +import { handleReadmeGeneration as oldHandler } from '@/lib/old-readme-generator'; + +export async function POST(request: Request) { + const useNewPipeline = process.env.USE_NEW_README_PIPELINE === 'true'; + + if (useNewPipeline) { + return newHandler(request); + } else { + return oldHandler(request); + } +} +``` + +### Step 3: A/B Testing + +Compare old vs new implementation: + +```typescript +export async function POST(request: Request) { + const body = await request.json(); + const { githubUrl, useNewPipeline } = body; + + if (useNewPipeline) { + return handleReadmeGeneration(request); + } else { + // Use old implementation + return oldReadmeGeneration(request); + } +} +``` + +## ๐Ÿ› ๏ธ Troubleshooting + +### Common Issues and Solutions + +#### 1. Token Limit Exceeded + +**Problem**: Even individual sections exceed token limits +**Solution**: Reduce `maxTokensPerSection` or simplify prompts + +```typescript +const generator = new MultiStepReadmeGenerator(apiKey, githubToken, { + maxTokensPerSection: 600, // Reduce from default 800 +}); +``` + +#### 2. Rate Limiting + +**Problem**: API rate limits exceeded +**Solution**: Reduce concurrency and add delays + +```typescript +const generator = new MultiStepReadmeGenerator(apiKey, githubToken, { + concurrentSections: 1, // Generate one section at a time +}); +``` + +#### 3. GitHub API Rate Limits + +**Problem**: Repository analysis fails due to rate limits +**Solution**: Provide GitHub token and implement caching + +```typescript +// Implement simple caching +const cache = new Map(); + +class CachedRepositoryAnalyzer extends RepositoryAnalyzer { + async analyzeRepository(owner: string, repo: string) { + const key = `${owner}/${repo}`; + + if (cache.has(key)) { + return cache.get(key); + } + + const result = await super.analyzeRepository(owner, repo); + cache.set(key, result); + + return result; + } +} +``` + +#### 4. Incomplete Sections + +**Problem**: Some sections are consistently incomplete +**Solution**: Increase retries or customize prompts + +```typescript +// Custom prompt for problematic section +const customPrompts = { + installation: `Generate concise installation instructions for "${metadata.name}". + + Context: ${structure.techStack.primary} project + + Requirements: + - Prerequisites (if any) + - Single command installation + - Verification step + + Keep it under 300 words. Return only markdown.`, +}; +``` + +### Debug Mode + +Enable detailed logging: + +```typescript +// Set environment variable +process.env.DEBUG_README_GENERATION = 'true'; + +// In the generator +if (process.env.DEBUG_README_GENERATION === 'true') { + console.log('Section generation details:', { + sectionId, + prompt: prompt.substring(0, 200) + '...', + result: result.success ? 'success' : 'failed', + tokensUsed: result.tokensUsed, + }); +} +``` + +## ๐Ÿ“ˆ Performance Optimizations + +### 1. Caching Strategy + +Implement Redis caching for repository analysis: + +```typescript +import Redis from 'ioredis'; + +const redis = new Redis(process.env.REDIS_URL); + +class CachedAnalyzer extends RepositoryAnalyzer { + async analyzeRepository(owner: string, repo: string) { + const key = `repo:${owner}:${repo}`; + const cached = await redis.get(key); + + if (cached) { + return JSON.parse(cached); + } + + const result = await super.analyzeRepository(owner, repo); + await redis.setex(key, 3600, JSON.stringify(result)); // 1 hour cache + + return result; + } +} +``` + +### 2. Background Processing + +For large repositories, use background jobs: + +```typescript +import Bull from 'bull'; + +const readmeQueue = new Bull('readme generation'); + +// API route for immediate response +export async function POST(request: Request) { + const { githubUrl } = await request.json(); + + const job = await readmeQueue.add('generate', { githubUrl }); + + return Response.json({ + jobId: job.id, + status: 'queued', + }); +} + +// Background worker +readmeQueue.process('generate', async (job) => { + const { githubUrl } = job.data; + const generator = new MultiStepReadmeGenerator(...); + + return generator.generateReadme(githubUrl); +}); +``` + +### 3. Streaming Responses + +Stream sections as they're generated: + +```typescript +export async function POST(request: Request) { + const { githubUrl } = await request.json(); + + const stream = new ReadableStream({ + async start(controller) { + const generator = new MultiStepReadmeGenerator(...); + + // Override assembler to stream results + const originalAssembler = generator.assembler; + generator.assembler.generateSectionsInBatches = async (...args) => { + // Stream each section as it's completed + // Implementation details... + }; + + const result = await generator.generateReadme(githubUrl); + controller.close(); + }, + }); + + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }, + }); +} +``` + +## ๐Ÿงช Testing + +### Unit Tests + +```typescript +// __tests__/readme-generator.test.ts +import { MultiStepReadmeGenerator, RepositoryAnalyzer } from '@/lib/multi-step-readme-generator'; + +describe('MultiStepReadmeGenerator', () => { + it('should generate complete README for public repository', async () => { + const generator = new MultiStepReadmeGenerator( + process.env.GEMINI_API_KEY, + process.env.GITHUB_TOKEN + ); + + const result = await generator.generateReadme( + 'https://github.com/octocat/Hello-World' + ); + + expect(result.success).toBe(true); + expect(result.readme).toContain('# Hello-World'); + expect(result.stats.sectionsGenerated).toBeGreaterThan(0); + }); +}); +``` + +### Integration Tests + +```typescript +describe('API Integration', () => { + it('should handle README generation request', async () => { + const response = await fetch('/api/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + githubUrl: 'https://github.com/octocat/Hello-World' + }), + }); + + const result = await response.json(); + + expect(response.status).toBe(200); + expect(result.success).toBe(true); + expect(result.readme).toBeDefined(); + }); +}); +``` + +## ๐Ÿ“š API Reference + +### Main Classes + +- **`MultiStepReadmeGenerator`**: Main orchestrator class +- **`RepositoryAnalyzer`**: Analyzes GitHub repositories +- **`SectionPlanner`**: Plans optimal README sections +- **`SectionGenerator`**: Generates individual sections +- **`ReadmeAssembler`**: Assembles and validates final README + +### Configuration Options + +```typescript +interface GenerationConfig { + maxRetries: number; // Default: 3 + maxTokensPerSection: number; // Default: 800 + temperature: number; // Default: 0.7 + concurrentSections: number; // Default: 3 + enableContinuation: boolean; // Default: true +} +``` + +### Section Types + +- **Critical**: `header`, `description`, `installation` +- **High**: `features`, `usage`, `api` +- **Medium**: `configuration`, `development`, `contributing`, `deployment` +- **Low**: `testing`, `examples` + +This comprehensive integration guide provides everything needed to successfully implement the multi-step README generation pipeline in the ReadmeGenAI project, solving the token limit issues while providing a more robust and reliable generation process. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9a1c38a..770db20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@radix-ui/react-slot": "^1.2.4", "@vercel/analytics": "^2.0.1", "lucide-react": "^0.577.0", - "next": "16.2.3", + "next": "^16.2.4", + "next-auth": "^4.24.14", "octokit": "^5.0.5", "react": "19.2.4", "react-dom": "19.2.4", @@ -91,6 +92,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -269,7 +271,6 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -341,9 +342,9 @@ "license": "MIT" }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -353,9 +354,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", "optional": true, "dependencies": { @@ -1092,9 +1093,9 @@ } }, "node_modules/@next/env": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.3.tgz", - "integrity": "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1108,9 +1109,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz", - "integrity": "sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" ], @@ -1124,9 +1125,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz", - "integrity": "sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ "x64" ], @@ -1140,9 +1141,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz", - "integrity": "sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ "arm64" ], @@ -1156,9 +1157,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz", - "integrity": "sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ "arm64" ], @@ -1172,9 +1173,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz", - "integrity": "sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ "x64" ], @@ -1188,9 +1189,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz", - "integrity": "sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ "x64" ], @@ -1204,9 +1205,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz", - "integrity": "sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" ], @@ -1220,9 +1221,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz", - "integrity": "sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" ], @@ -1394,6 +1395,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -1634,6 +1636,15 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -2327,7 +2338,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2348,7 +2358,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -2424,8 +2433,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/aws-lambda": { "version": "8.10.160", @@ -2526,6 +2534,7 @@ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -2535,6 +2544,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2545,6 +2555,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2600,6 +2611,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -3121,6 +3133,7 @@ "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", @@ -3265,6 +3278,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3305,7 +3319,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3604,9 +3617,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3649,6 +3662,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3823,6 +3837,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4048,8 +4071,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -4319,6 +4341,7 @@ "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -4404,9 +4427,9 @@ "license": "MIT" }, "node_modules/eslint-config-next/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4455,6 +4478,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4590,9 +4614,9 @@ } }, "node_modules/eslint-config-next/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4943,9 +4967,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -6051,6 +6075,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6490,7 +6523,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -7519,12 +7551,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", - "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", "license": "MIT", + "peer": true, "dependencies": { - "@next/env": "16.2.3", + "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -7538,14 +7571,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.3", - "@next/swc-darwin-x64": "16.2.3", - "@next/swc-linux-arm64-gnu": "16.2.3", - "@next/swc-linux-arm64-musl": "16.2.3", - "@next/swc-linux-x64-gnu": "16.2.3", - "@next/swc-linux-x64-musl": "16.2.3", - "@next/swc-win32-arm64-msvc": "16.2.3", - "@next/swc-win32-x64-msvc": "16.2.3", + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { @@ -7571,6 +7604,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.14", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.14.tgz", + "integrity": "sha512-YRz6xFDXKUwiXSMMChbrBEWyFktZ1qZXEgeSHQQ3nsy08B4c/xLk6REeutRsIFwkjY/1+ShHnu07DN3JeJguig==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next-sitemap": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz", @@ -7660,6 +7725,12 @@ "dev": true, "license": "MIT" }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7670,6 +7741,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -7816,6 +7896,48 @@ "node": ">= 20" } }, + "node_modules/oidc-token-hash": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7962,9 +8084,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -8027,6 +8149,35 @@ "node": ">=4" } }, + "node_modules/preact": { + "version": "10.29.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.1.tgz", + "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8043,7 +8194,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -8059,7 +8209,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8072,8 +8221,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", @@ -8133,6 +8281,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8142,6 +8291,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8996,7 +9146,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -9075,11 +9226,12 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9268,6 +9420,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9508,6 +9661,15 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -9556,6 +9718,7 @@ "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -9647,6 +9810,7 @@ "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", @@ -9732,9 +9896,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9912,6 +10076,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 44aa724..fc09f48 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@radix-ui/react-slot": "^1.2.4", "@vercel/analytics": "^2.0.1", "lucide-react": "^0.577.0", - "next": "16.2.3", + "next": "^16.2.4", + "next-auth": "^4.24.14", "octokit": "^5.0.5", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..8603b53 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,9 @@ +# * +User-agent: * +Allow: / + +# Host +Host: https://readmegen-ai.vercel.app + +# Sitemaps +Sitemap: https://readmegen-ai.vercel.app/sitemap.xml diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml new file mode 100644 index 0000000..6ac8e51 --- /dev/null +++ b/public/sitemap-0.xml @@ -0,0 +1,8 @@ + + +https://readmegen-ai.vercel.app2026-04-04T09:31:17.224Zweekly1 +https://readmegen-ai.vercel.app/docs2026-04-04T09:31:17.224Zmonthly0.8 +https://readmegen-ai.vercel.app/examples2026-04-04T09:31:17.224Zmonthly0.8 +https://readmegen-ai.vercel.app/features2026-04-04T09:31:17.224Zmonthly0.8 +https://readmegen-ai.vercel.app/generate2026-04-04T09:31:17.224Zweekly0.9 + \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..b3a9e11 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,4 @@ + + +https://readmegen-ai.vercel.app/sitemap-0.xml + \ No newline at end of file diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..7b38c1b --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index acb17e5..5c51aa9 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -1,40 +1,41 @@ -import { NextResponse } from "next/server"; +import { getToken } from "next-auth/jwt"; +import { NextRequest, NextResponse } from "next/server"; import { getGeminiModel } from "@/lib/gemini"; -import { getRepoData, getRepoContents } from "@/lib/octokit"; -import { SUPPORTED_LANGUAGES } from "@/constants/languages"; +import { getRepoSnapshot, RepoAccessError } from "@/lib/octokit"; export const dynamic = "force-dynamic"; /** - * AI README Generation Endpoint - * Optimized for data accuracy, clean prompt interpolation, and multi-language support. + * Enhanced Multi-Step README Generation Endpoint * - * @param {Request} req - The incoming Next.js/standard Web API Request object containing the repo URL and optional language. + * @param {NextRequest} req - The incoming Next.js request object containing the repo URL and optional language. * @returns {Promise} A JSON response containing the generated Markdown or an error message. */ -export async function POST(req: Request) { +export async function POST(req: NextRequest) { let rawUrl: string; let language: string; + let ackPrivateRepo = false; try { const body = await req.json(); rawUrl = body.url; language = body.language || "English"; + ackPrivateRepo = Boolean(body.ackPrivateRepo); } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - try { - const trimmedUrl = rawUrl?.trim(); - if (!trimmedUrl) { + // Validate required fields + if (!githubUrl) { return NextResponse.json( { error: "GitHub URL is required" }, { status: 400 }, ); } + // Validate GitHub URL format let parsedUrl: URL; try { - parsedUrl = new URL(trimmedUrl); + parsedUrl = new URL(githubUrl.trim()); } catch { return NextResponse.json( { error: "Please provide a valid URL" }, @@ -63,13 +64,34 @@ export async function POST(req: Request) { ); } - const [repoInfo, repoContents] = await Promise.all([ - getRepoData(owner, repo), - getRepoContents(owner, repo), - ]); + const token = await getToken({ + req, + secret: process.env.NEXTAUTH_SECRET, + }); + const accessToken = + typeof token?.accessToken === "string" ? token.accessToken : undefined; + + const { repoInfo, repoContents } = await getRepoSnapshot( + owner, + repo, + accessToken, + ); + + const isPrivateRepo = Boolean(repoInfo?.private); + if (isPrivateRepo && !ackPrivateRepo) { + return NextResponse.json( + { + error: "private_repo_consent_required", + message: + "This repository appears to be private. Confirm consent to send private repository data to the AI model by re-submitting with { ackPrivateRepo: true }.", + authRequired: Boolean(accessToken), + }, + { status: 403 }, + ); + } const files = Array.isArray(repoContents) - ? repoContents.map((f: { name: string }) => f.name) + ? repoContents.map((f: { path: string }) => f.path) : []; const fileListString = files.length > 0 ? files.join(", ") : "Standard repository structure"; @@ -158,12 +180,50 @@ export async function POST(req: Request) { return NextResponse.json({ markdown: cleanMarkdown }); } catch (error: unknown) { + if (error instanceof RepoAccessError) { + return NextResponse.json( + { + error: error.message, + authRequired: error.code === "AUTH_REQUIRED", + }, + { status: error.status }, + ); + } + const message = error instanceof Error ? error.message : "Internal Server Error"; console.error("README Generation Failed:", message); + // Return successful result with enhanced metadata + return NextResponse.json({ + success: true, + markdown: result.readme, // Keep 'markdown' key for compatibility with existing frontend + stats: { + sectionsGenerated: result.stats.sectionsGenerated, + sectionsTotal: result.stats.sectionsTotal, + tokensUsed: result.stats.tokensUsed, + timeElapsed: result.stats.timeElapsed, + generationMethod: "multi-step", // Indicate the method used + }, + metadata: { + name: result.metadata?.name, + description: result.metadata?.description, + language: result.metadata?.language, + stars: result.metadata?.stars, + license: result.metadata?.license, + projectType: result.structure?.projectType, + techStack: result.structure?.techStack.primary, + frameworks: result.structure?.techStack.frameworks, + }, + warnings: result.errors.length > 0 ? result.errors : undefined, + }); + } catch (error) { + console.error("Multi-step README generation API error:", error); return NextResponse.json( - { error: "Failed to generate README. Check your URL and try again." }, + { + error: "Internal server error in multi-step README generation", + message: error instanceof Error ? error.message : "Unknown error", + }, { status: 500 }, ); } diff --git a/src/app/api/generate/route.ts.backup b/src/app/api/generate/route.ts.backup new file mode 100644 index 0000000..acb17e5 --- /dev/null +++ b/src/app/api/generate/route.ts.backup @@ -0,0 +1,170 @@ +import { NextResponse } from "next/server"; +import { getGeminiModel } from "@/lib/gemini"; +import { getRepoData, getRepoContents } from "@/lib/octokit"; +import { SUPPORTED_LANGUAGES } from "@/constants/languages"; + +export const dynamic = "force-dynamic"; + +/** + * AI README Generation Endpoint + * Optimized for data accuracy, clean prompt interpolation, and multi-language support. + * + * @param {Request} req - The incoming Next.js/standard Web API Request object containing the repo URL and optional language. + * @returns {Promise} A JSON response containing the generated Markdown or an error message. + */ +export async function POST(req: Request) { + let rawUrl: string; + let language: string; + try { + const body = await req.json(); + rawUrl = body.url; + language = body.language || "English"; + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + try { + const trimmedUrl = rawUrl?.trim(); + if (!trimmedUrl) { + return NextResponse.json( + { error: "GitHub URL is required" }, + { status: 400 }, + ); + } + + let parsedUrl: URL; + try { + parsedUrl = new URL(trimmedUrl); + } catch { + return NextResponse.json( + { error: "Please provide a valid URL" }, + { status: 400 }, + ); + } + + if ( + parsedUrl.hostname !== "github.com" && + parsedUrl.hostname !== "www.github.com" + ) { + return NextResponse.json( + { error: "Only GitHub URLs are supported" }, + { status: 400 }, + ); + } + + const pathSegments = parsedUrl.pathname.split("/").filter(Boolean); + const owner = pathSegments[0]; + const repo = pathSegments[1]; + + if (!owner || !repo) { + return NextResponse.json( + { error: "URL must include owner and repository name" }, + { status: 400 }, + ); + } + + const [repoInfo, repoContents] = await Promise.all([ + getRepoData(owner, repo), + getRepoContents(owner, repo), + ]); + + const files = Array.isArray(repoContents) + ? repoContents.map((f: { name: string }) => f.name) + : []; + const fileListString = + files.length > 0 ? files.join(", ") : "Standard repository structure"; + + // Tech Stack detection logic + const hasNode = files.includes("package.json"); + const hasPython = + files.includes("requirements.txt") || files.includes("setup.py"); + const hasDocker = + files.includes("Dockerfile") || files.includes("docker-compose.yml"); + + // Fix: Cleanly joined Tech Stack labels + const stackLabels = + [ + hasNode && "Node.js Environment", + hasPython && "Python Environment", + hasDocker && "Containerized", + ] + .filter(Boolean) + .join(", ") || "Generic Software Environment"; + + // Fix: Dynamic License detection + const licenseName = + repoInfo?.license?.name || + repoInfo?.license?.spdx_id || + "the repository's license file"; + + const model = getGeminiModel(); + + // Fix: Prompt updated with neutral fallbacks and dynamic license + const prompt = ` +**Role**: You are a Principal Solutions Architect and World-Class Technical Writer. +**Task**: Generate a professional, high-conversion README.md for the GitHub repository: "${repo}" in the following language: **${language}**. + +--- +### 1. PROJECT CONTEXT (VERIFIED DATA) +- **Project Name**: ${repo} +- **Description**: ${repoInfo?.description || "No description provided."} +- **Primary Language**: ${repoInfo?.language || "Language unknown"} +- **Detected Root Files**: ${fileListString} +- **Tech Stack Context**: ${stackLabels} + +--- +### 2. STRICT README STRUCTURE REQUIREMENTS + +1. **Visual Header**: + - Center-aligned H1 with project name. + - A compelling 1-sentence tagline describing the **Value Proposition**. + - A centered row of Shields.io badges (Build, License, PRs Welcome, Stars). + +2. **The Strategic "Why" (Overview)**: + - **The Problem**: Use a blockquote to describe the real-world pain point this project solves. + - **The Solution**: Explain how this project provides a superior outcome for the user. + +3. **Key Features**: + - Minimum 5 features. Use emojis and focus on **User Benefits**. + +4. **Technical Architecture**: + - Provide a table of the tech stack: | Technology | Purpose | Key Benefit |. + - Create a tree-style directory structure code block using ๐Ÿ“ for folders and ๐Ÿ“„ for files based on the file manifest provided. + +5. **Operational Setup**: + - **Prerequisites**: List required runtimes. + - **Installation**: Provide step-by-step terminal commands. + ${hasNode ? "- Use npm/yarn/pnpm since package.json was detected." : ""} + ${hasPython ? "- Use pip/venv since Python markers were detected." : ""} + - **Environment**: If any .env or config files are in the manifest, include a configuration section. + +6. **Community & Governance**: + - Professional "Contributing" section (Fork -> Branch -> PR). + - Detailed "License" section: Reference ${licenseName} and provide a summary of permissions. + +--- +### 3. TONE & STYLE +- **Tone**: Authoritative, polished, and developer-centric. +- **Visuals**: Extensive use of Markdown formatting. +- **Constraint**: Return ONLY the raw Markdown. No conversational filler. + `; + + const result = await model.generateContent(prompt); + const response = await result.response; + const markdown = response.text().trim(); + const cleanMarkdown = markdown + .replace(/^```(markdown|md)?\n/, "") + .replace(/\n```$/, ""); + + return NextResponse.json({ markdown: cleanMarkdown }); + } catch (error: unknown) { + const message = + error instanceof Error ? error.message : "Internal Server Error"; + console.error("README Generation Failed:", message); + + return NextResponse.json( + { error: "Failed to generate README. Check your URL and try again." }, + { status: 500 }, + ); + } +} diff --git a/src/app/generate/GeneratePageClient.tsx b/src/app/generate/GeneratePageClient.tsx index b430b3e..a36f842 100644 --- a/src/app/generate/GeneratePageClient.tsx +++ b/src/app/generate/GeneratePageClient.tsx @@ -15,6 +15,10 @@ interface GeneratePageProps { export default function GeneratePageClient({ repoSlug }: GeneratePageProps) { const [markdown, setMarkdown] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [authRequired, setAuthRequired] = useState(false); + const [privateRepoConsentRequired, setPrivateRepoConsentRequired] = + useState(false); // Optional: Update document title for SPA navigation useEffect(() => { @@ -29,28 +33,37 @@ export default function GeneratePageClient({ repoSlug }: GeneratePageProps) { const handleGenerate = async ( githubUrl: string, language: string = "English", + ackPrivateRepo: boolean = false, ) => { setIsLoading(true); setMarkdown(""); + setErrorMessage(null); + setAuthRequired(false); + setPrivateRepoConsentRequired(false); try { const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ url: githubUrl, language }), + body: JSON.stringify({ url: githubUrl, language, ackPrivateRepo }), }); if (!response.ok) { const errorText = await response.text(); let errorMessage: string; + let requiresAuth = false; try { const errorData = JSON.parse(errorText); errorMessage = errorData.error || errorData.message || errorText; + requiresAuth = Boolean(errorData.authRequired); + setPrivateRepoConsentRequired( + errorData.error === "private_repo_consent_required", + ); } catch { errorMessage = errorText || response.statusText; } - throw new Error( - `[${response.status} ${response.statusText}]: ${errorMessage}`, - ); + + setAuthRequired(requiresAuth); + throw new Error(errorMessage); } const data = await response.json(); @@ -64,7 +77,9 @@ export default function GeneratePageClient({ repoSlug }: GeneratePageProps) { } } catch (error: unknown) { console.error("Generation Error:", error); - alert(error instanceof Error ? error.message : "Something went wrong"); + setErrorMessage( + error instanceof Error ? error.message : "Something went wrong", + ); } finally { setIsLoading(false); } @@ -85,6 +100,9 @@ export default function GeneratePageClient({ repoSlug }: GeneratePageProps) { isLoading={isLoading} initialValue={repoSlug ? `https://github.com/${repoSlug}` : ""} ariaLabel="Enter GitHub repository URL to generate README" + serverError={errorMessage} + authRequired={authRequired} + privateRepoConsentRequired={privateRepoConsentRequired} /> diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1db0de7..e69260f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { Analytics } from "@vercel/analytics/next"; import pkg from "../../package.json"; +import { Providers } from "./providers"; import "./globals.css"; const geistSans = Geist({ @@ -117,7 +118,6 @@ export default function RootLayout({ return ( - {/* JSON-LD structured data */}